項目build成功後,刷新整個項目,會發現多出一個output目錄:java
爲了讓應用跑起來,能夠檢查一下output\build\conf下是否已經有配置文件,這些文件實際是從項目根路徑conf目錄下拷貝過來的。shell
找到BootStarp.java文件,Debug前加入默認的catalina home路徑做爲啓動參數。apache
路徑設置爲output下build的絕對路徑。好比我本身的機器設置的值是-Dcatalina.home="W:\workspace\tc7.0.70\output\build"bootstrap
這樣就能夠愉快的在文件中加入斷點Debug源碼分析了。運行以後的效果圖: OK,源碼到此運行成功,完美~tomcat
上面運行源碼用的BootStarp.java這個類中的main方法(後面再對這個main方法作分析),實際上咱們在用Tomcat的時候,大部分都是使用腳本文件startup.sh、startup.bat、shutdown.sh、shutdown.bat等腳本或者批處理命令來啓動Tomcat的.你們必定知道改如何使用它,可是它們到底是如何實現的,下面就一點一點的分析。app
因爲在生產環境中,Tomcat通常部署在Linux系統下,因此將以startup.sh和shutdown.sh等shell腳本爲準,對Tomcat的啓動與中止進行分析。dom
Linux下啓動Tomcat的命令:socket
sh startup.sh
下面將從shell腳本startup.sh開始分析Tomcat的啓動過程。ide
startup.sh腳本代碼清單:oop
# Better OS/400 detection: see Bugzilla 31132 os400=false case "`uname`" in OS400*) os400=true;; esac # resolve links - $0 may be a softlink PRG="$0" while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`/"$link" fi done PRGDIR=`dirname "$PRG"` EXECUTABLE=catalina.sh # Check that target executable exists if $os400; then # -x will Only work on the os400 if the files are: # 1. owned by the user # 2. owned by the PRIMARY group of the user # this will not work if the user belongs in secondary groups eval else if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then echo "Cannot find $PRGDIR/$EXECUTABLE" echo "The file is absent or does not have execute permission" echo "This file is needed to run this program" exit 1 fi fi exec "$PRGDIR"/"$EXECUTABLE" start "$@"
從代碼清單能夠看出有兩個主要的變量,分別是:
1. PRGDIR:當前shell腳本所在的路徑; 2. EXECUTABLE:腳本catalina.sh。
exec "$PRGDIR"/"$EXECUTABLE" start "$@"
咱們知道執行了shell腳本catalina.sh,而且傳遞參數start。
catalina.sh 腳本代碼(部分)清單:
shift touch "$CATALINA_OUT" if [ "$1" = "-security" ] ; then if [ $have_tty -eq 1 ]; then echo "Using Security Manager" fi shift eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \ -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \ -Djava.security.manager \ -Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \ -Dcatalina.base="\"$CATALINA_BASE\"" \ -Dcatalina.home="\"$CATALINA_HOME\"" \ -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \ org.apache.catalina.startup.Bootstrap "$@" start \ >> "$CATALINA_OUT" 2>&1 "&" else eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \ -Djava.endorsed.dirs="\"$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 "&" fi if [ ! -z "$CATALINA_PID" ]; then echo $! > "$CATALINA_PID" fi echo "Tomcat started."
從上面能夠看出,腳本最終使用java命令執行了org.apache.catalina.startup.Bootstrap類中的main方法,參數也是start。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.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")) { //傳遞參數爲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); } }
當傳遞參數start的時候,command等於start,此時main方法的執行步驟以下:
初始化Bootstrap
public void init() throws Exception{ // Set Catalina path setCatalinaHome(); //1.設置Catalina路徑,默認爲Tomcat的根目錄 setCatalinaBase(); initClassLoaders();//2.初始化Tomcat的類加載器 Thread.currentThread().setContextClassLoader(catalinaLoader);//3.並設置線程上下文類加載器 SecurityClassLoad.securityClassLoad(catalinaLoader); // Load our startup class and call its process() method if (log.isDebugEnabled()) log.debug("Loading startup class"); //4.用反射實例化org.apache.catalina.startup.Catalina對象,而且使用反射調用其setParentClassLoader方法,給Catalina對象設置Tomcat類加載體系的頂級加載器(Java自帶的三種類加載器除外) Class<?> startupClass = catalinaLoader.loadClass ("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.newInstance(); // Set the shared extensions class loader if (log.isDebugEnabled()) log.debug("Setting startup class properties"); 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 = startupInstance; }
加載、解析server.xml配置文件
當傳遞參數start的時候,會調用Bootstrap的load方法:
/** * Load daemon. */ private void load(String[] arguments) throws Exception { // Call the load() method String methodName = "load"; Object param[]; Class<?> paramTypes[]; if (arguments==null || arguments.length==0) { paramTypes = null; param = null; } else { paramTypes = new Class[1]; paramTypes[0] = arguments.getClass(); param = new Object[1]; param[0] = arguments; } Method method = catalinaDaemon.getClass().getMethod(methodName, paramTypes);//用反射調用catalinaDaemon(類型是Catalina)的load方法加載和解析server.xml配置文件。 if (log.isDebugEnabled()) log.debug("Calling startup class " + method); method.invoke(catalinaDaemon, param); }
備註:如何加載和解析server.xml配置文件,後面會博客會陸續給出。
啓動Tomcat
當傳遞參數start的時候,調用Bootstrap的load方法以後會接着調用start方法:
/** * Start the Catalina daemon. */ public void start() throws Exception { if( catalinaDaemon==null ) init(); Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);//啓動Tomcat,此方法實際是用反射調用了catalinaDaemon(類型是Catalina)的start方法 method.invoke(catalinaDaemon, (Object [])null); }
Catalina的start方法以下:
/** * Start a new server instance. */ public void start() { //1.驗證Server容器是否已經實例化 if (getServer() == null) { load(); //若是沒有實例化Server容器,還會再次調用Catalina的load方法加載和解析server.xml,這也說明Tomcat只容許Server容器經過配置在server.xml的方式生成,用戶也能夠本身實現Server接口建立自定義的Server容器以取代默認的StandardServer。 } if (getServer() == null) { log.fatal("Cannot start server. Server instance is not configured."); return; } long t1 = System.nanoTime(); // Start the new server try { getServer().start(); //2.啓動Server容器 } catch (LifecycleException e) { log.fatal(sm.getString("catalina.serverStartFail"), e); try { getServer().destroy(); } catch (LifecycleException e1) { log.debug("destroy() failed for failed Server ", e1); } return; } long t2 = System.nanoTime(); if(log.isInfoEnabled()) { log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms"); } // Register shutdown hook if (useShutdownHook) { if (shutdownHook == null) { shutdownHook = new CatalinaShutdownHook();//3.設置關閉鉤子 } Runtime.getRuntime().addShutdownHook(shutdownHook); // If JULI is being used, disable JULI's shutdown hook since // shutdown hooks run in parallel and log messages may be lost // if JULI's hook completes before the CatalinaShutdownHook() LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).setUseShutdownHook( false); } } if (await) { await();//4.最後調用Catalina的await方法循環等待接收Tomcat的shutdown命令 stop();//5.若是Tomcat運行正常且沒有收到shutdown命令,是不會向下執行此方法的,當接收到shutdown命令,Catalina的await方法會退出循環等待,而後順序執行stop方法中止Tomcat } }
Catalina的await方法實際只是代理執行了Server容器的await方法。
/** * Await and shutdown. */ public void await() { getServer().await(); }
以Server的默認實現StandardServer爲例,其await方法以下:
@Override public void await() { // Negative values - don't wait on port - tomcat is embedded or we just don't like ports if( port == -2 ) { // undocumented yet - for embedding apps that are around, alive. return; } if( port==-1 ) { try { awaitThread = Thread.currentThread(); while(!stopAwait) { try { Thread.sleep( 10000 ); } catch( InterruptedException ex ) { // continue and check the flag } } } finally { awaitThread = null; } return; } // Set up a server socket to wait on try { awaitSocket = new ServerSocket(port, 1, InetAddress.getByName(address));//建立socket鏈接的服務端對象ServerSocket } 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) { ServerSocket serverSocket = awaitSocket; if (serverSocket == null) { break; } // Wait for the next connection Socket socket = null; StringBuilder command = new StringBuilder();//建立一個對象循環接收socket中的字符 try { InputStream stream; long acceptStartTime = System.currentTimeMillis(); try { 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; } // Read a set of characters from the socket 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; } if (ch < 32) // Control character or EOF terminates loop 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 } } // Match against our command string boolean match = command.toString().equals(shutdown); if (match) { //若是接收到的命令與SHUTDOWN匹配(因爲使用了equals,因此shutdown命令必須是大寫的),那麼退出循環等待 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 { serverSocket.close(); } catch (IOException e) { // Ignore } } } }
至此,Tomcat啓動完畢。
備註:如何啓動server,這裏不作過多解釋,後面會有專門的博客介紹《容器啓動過程分析》。