Tomcat源碼分析--啟動
點擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時間送達(dá)
首先找到catalina.sh中的啟動腳本:
eval $_NOHUP "\"$_RUNJAVA\"" "\"$CATALINA_LOGGING_CONFIG\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \
-D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \
-classpath "\"$CLASSPATH\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" start \
>> "$CATALINA_OUT" 2>&1 "&"啟動類為org.apache.catalina.startup.Bootstrap,在Bootstrap類中找到main方法:
public static void main(String args[]) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
//初始化Bootstrap
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to prevent
// a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null==daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
初始化Bootstrap
在main方法中會首先初始化Bootstrap,然后解析command,根據(jù)不同的command執(zhí)行不同的邏輯處理,通過catalina.sh啟動Tomcat傳人的參數(shù)是"start",不過看代碼中command默認(rèn)也是start。我們首先看Bootstrap的初始化方法bootstrap.init():
public void init() throws Exception {
//初始化類加載器
initClassLoaders();
//設(shè)置TCCL
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
//加載并創(chuàng)建啟動類:Catalina
Class<?> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
//通過反射設(shè)置parentClassLoader屬性
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
//賦值啟動類到catalinaDaemon屬性
catalinaDaemon = startupInstance;
}
初始化動作主要是初始化類加載器,然后加載并實例化啟動類(Catalina)。這里初始化的類加載器是common、shared、catalina三個類加載器,沒有初始化WebApp的類加載器:
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
6.x以后的版本合并了share、common等目錄,可以根據(jù)參數(shù)server.loader、share.loader等決定是否啟動老的結(jié)構(gòu),以創(chuàng)建sharedClassLoader為例:
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
//創(chuàng)建shareClassLoader時,name參數(shù)是share
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
//如果沒有配置share.loader,那么使用直接返回父類加載器
return parent;
......
//創(chuàng)建shareClassLoader的邏輯
}
再回到bootstrap.init()的邏輯,創(chuàng)建好類加載器之后,catalinaDemon已經(jīng)被賦值為一個Catalina對象。
解析command參數(shù)
Bootstrap初始化工作完成后,進(jìn)入command==start的處理邏輯:
if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
}
一共就三行代碼,首先是setAwait(true),看看Bootstrap.setAwait方法:
public void setAwait(boolean await)
throws Exception {
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Boolean.TYPE;
Object paramValues[] = new Object[1];
paramValues[0] = Boolean.valueOf(await);
Method method =
catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
method.invoke(catalinaDaemon, paramValues);
}通過反射調(diào)用了catalinaDaemon的setAwait方法,前面提到了,catalinaDaemon在Bootstrap初始化的時候生成,我們這里直接看它的setAwait方法:
protected boolean await = false;
public void setAwait(boolean b) {
await = b;
}方法很簡單,就是設(shè)置await屬性,這里將其設(shè)置為了true。然后再回到Bootstrap處理start指令的流程中:
daemon.setAwait(true);//將Catalina的await屬性設(shè)置為true
daemon.load(args);//加載初始化Tomcat的核心組件
daemon.start();//解析文件,啟動組件啟動組件
daemon.load方法主要加載Tomcat的核心組件,這個部分后面再分析,我們直接看daemon.start()方法是如何啟動組件的:
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}還是通過反射調(diào)用Catalina的start方法(部分代碼):
protected boolean useShutdownHook = true;
public void start() {
if (getServer() == null) {
//生成server實例
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
// 啟動server
getServer().start();
//注冊一個shutdownhOOK
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
if (await) {
//await
await();
stop();
}
}load()方法會解析server.xml節(jié)點,對應(yīng)初始化很多對象,包括server、engine、host、connector等等。最主要的就是頂層Server節(jié)點,對應(yīng)的StandardServer對象:
digester.addObjectCreate("Server","org.apache.catalina.core.StandardServer","className");這部分流程后面再分析。啟動server后,首先注冊了一個shutdownHook(關(guān)于shutdownHook可以參考JAVA之ShutdownHook源碼分析),主要應(yīng)對不是通過socket正常退出的情況。前面通過反射設(shè)置了await屬性為true,所以進(jìn)入await()方法:
public void await() {
getServer().await();
}阻塞主線程
Catalina的await方法又調(diào)用了getServer的await方法,server在前面的load階段已經(jīng)創(chuàng)建,指定的是org.apache.catalina.core.StandardServer,進(jìn)入它的await方法:
public void await() {
if( port == -2 ) {
//內(nèi)嵌的tomcat,直接返回
return;
}
if( port==-1 ) {
//如果==-1,根據(jù)stopAwait參數(shù)間隔10秒死檢查循環(huán)
try {
awaitThread = Thread.currentThread();
while(!stopAwait) {
try {
Thread.sleep( 10000 );
} catch( InterruptedException ex ) {
// continue and check the flag
}
}
} finally {
awaitThread = null;
}
return;
}
//根據(jù)port創(chuàng)建一個ServerSocket,如果port小于0,會拋出異常
try {
awaitSocket = new ServerSocket(port, 1,
InetAddress.getByName(address));
} catch (IOException e) {
log.error("StandardServer.await: create[" + address
+ ":" + port
+ "]: ", e);
return;
}
try {
awaitThread = Thread.currentThread();
// Loop waiting for a connection and a valid command
while (!stopAwait) {
//根據(jù)標(biāo)識死循環(huán)
ServerSocket serverSocket = awaitSocket;
if (serverSocket == null) {
break;
}
// Wait for the next connection
Socket socket = null;
StringBuilder command = new StringBuilder();
try {
InputStream stream;
long acceptStartTime = System.currentTimeMillis();
try {
//調(diào)用accept方法阻塞,等待連接
socket = serverSocket.accept();
socket.setSoTimeout(10 * 1000); // Ten seconds
stream = socket.getInputStream();
} catch (SocketTimeoutException ste) {
// This should never happen but bug 56684 suggests that
// it does.
log.warn(sm.getString("standardServer.accept.timeout",
Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);
continue;
} catch (AccessControlException ace) {
log.warn("StandardServer.accept security exception: "
+ ace.getMessage(), ace);
continue;
} catch (IOException e) {
if (stopAwait) {
// Wait was aborted with socket.close()
break;
}
log.error("StandardServer.await: accept: ", e);
break;
}
// 從socket中讀取數(shù)據(jù),shutdown是一個字符串可以在server.xml中配置,默認(rèn)為SHUTDOWN
int expected = 1024; // Cut off to avoid DoS attack
while (expected < shutdown.length()) {
if (random == null)
random = new Random();
expected += (random.nextInt() % 1024);
}
while (expected > 0) {
int ch = -1;
try {
ch = stream.read();
} catch (IOException e) {
log.warn("StandardServer.await: read: ", e);
ch = -1;
}
// Control character or EOF (-1) terminates loop
if (ch < 32 || ch == 127) {
break;
}
command.append((char) ch);
expected--;
}
} finally {
// Close the socket now that we are done with it
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
// Ignore
}
}
boolean match = command.toString().equals(shutdown);
if (match) {
//如果讀取的數(shù)據(jù)是shutdown對應(yīng)的字符串就跳出死循環(huán)
log.info(sm.getString("standardServer.shutdownViaPort"));
break;
} else
log.warn("StandardServer.await: Invalid command '"
+ command.toString() + "' received");
}
} finally {
ServerSocket serverSocket = awaitSocket;
awaitThread = null;
awaitSocket = null;
// Close the server socket and return
if (serverSocket != null) {
try {
//斷開socket連接
serverSocket.close();
} catch (IOException e) {
// Ignore
}
}
}
}
代碼邏輯很長,其實主要就干幾件事:
如果port==-2,表示是內(nèi)嵌的tomcat,直接返回
如果port==-1,根據(jù)標(biāo)識死循環(huán)阻塞
否則根據(jù)port創(chuàng)建一個ServerSocket,并且循環(huán)阻塞等待socket連接,直到接收到shutdown指令
shutdown指令和對應(yīng)的port可以在server.xml中自行配置:
<Server port="8005" shutdown="SHUTDOWN">
......
</Server>就通過這樣的方式阻塞主線程,直到shutdown。await方法返回后,調(diào)用Catalina.stop()方法停止服務(wù)器。大體流程如下:

————————————————
版權(quán)聲明:本文為CSDN博主「黃智霖-blog」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:
https://blog.csdn.net/huangzhilin2015/article/details/115048022
鋒哥最新SpringCloud分布式電商秒殺課程發(fā)布
??????
??長按上方微信二維碼 2 秒
感謝點贊支持下哈 
