【转】Tomcat7启动的总过程 (有时间自己写下tomcat8的)
首先,說明tomcat8和tomcat7的啟動過程不一樣,這篇是針對tomcat7的。
Tomcat啟動的總過程
通過上面的介紹,我們總體上清楚了各個組件的生命周期的各個階段具體都是如何運作的。接下來我們就來看看,Tomcat具體是如何一步步啟動起來的。我們都知道任何Java程序都有一個main函數入口,Tomcat中的main入口是org.apache.catalina.startup.Bootstrap#main,下面我們就來分析一下它的代碼:
org.apache.catalina.startup.Bootstrap#main
public static void main(String args[]) {if (daemon == null) {// Don't set daemon until init() has completed// 1 Bootstrap bootstrap = new Bootstrap();try {// 2 bootstrap.init();} catch (Throwable t) {handleThrowable(t);t.printStackTrace();return;}// 3daemon = 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")) {// 4daemon.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 reportingif (t instanceof InvocationTargetException &&t.getCause() != null) {t = t.getCause();}handleThrowable(t);t.printStackTrace();System.exit(1);}}下面我們逐一來分析一下上述代碼中標注了數字的地方:
接下來我們分別分析一下BootStrap的init,load,start方法具體做了哪些工作。
BootStrap#init方法
首先來看org.apache.catalina.startup.Bootstrap#init方法,它的代碼如下:
org.apache.catalina.startup.Bootstrap#init
public void init()throws Exception{// Set Catalina path setCatalinaHome();setCatalinaBase();initClassLoaders();Thread.currentThread().setContextClassLoader(catalinaLoader);SecurityClassLoad.securityClassLoad(catalinaLoader);// Load our startup class and call its process() methodif (log.isDebugEnabled())log.debug("Loading startup class");// 1Class<?> startupClass =catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");Object startupInstance = startupClass.newInstance();// Set the shared extensions class loaderif (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);// 2 method.invoke(startupInstance, paramValues);// 3catalinaDaemon = startupInstance;}下面我們重點逐一來分析一下上述代碼中標注了數字的地方:
BootStrap#load
接下來我們再來看看org.apache.catalina.startup.Bootstrap#load方法,通過查看源代碼,我們知道此方法通過反射調用了org.apache.catalina.startup.Catalina#load方法,那我們就來看看Catalina的load方法,Catalina#load方法代碼如下:
org.apache.catalina.startup.Catalina#load
public void load() {// 1 Digester digester = createStartDigester();InputSource inputSource = null;InputStream inputStream = null;File file = null;try {file = configFile();inputStream = new FileInputStream(file);inputSource = new InputSource(file.toURI().toURL().toString());} catch (Exception e) {if (log.isDebugEnabled()) {log.debug(sm.getString("catalina.configFail", file), e);}}try {inputSource.setByteStream(inputStream);digester.push(this);digester.parse(inputSource);} catch (SAXParseException spe) {log.warn("Catalina.start using " + getConfigFile() + ": " +spe.getMessage());return;} catch (Exception e) {log.warn("Catalina.start using " + getConfigFile() + ": " , e);return;} finally {try {inputStream.close();} catch (IOException e) {// Ignore }}getServer().setCatalina(this);// Stream redirection initStreams();// Start the new servertry {// 2 getServer().init();} catch (LifecycleException e) {if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {throw new java.lang.Error(e);} else {log.error("Catalina.start", e);}}}上面的代碼,我只保留了主流程核心的代碼,下面我們重點逐一來分析一下上述代碼中標注了數字的地方:
大家可以自行查看下源代碼,我們會發現如下的一個調用流程:
init call stack
org.apache.catalina.core.StandardServer#init ->org.apache.catalina.core.StandardService#init -->org.apache.catalina.connector.Connector#init -->org.apache.catalina.core.StandardEngine#init因為StandardService,Connector,StandardEngine實現了LifeCycle接口,因此符合我們上文所獲的生命周期的管理,最終都是通過他們自己實現的initInternal方法進行初始化
讀到這里的時候,我想大家應該和我一樣,以為StandardEngine#init方法會調用StandardHost#init方法,但是當我們查看StandardEngine#init方法的時候,發現并沒有進行StandardHost的初始化,它到底做了什么呢?讓我們來具體分析一下,我們首先拿StanderEngine的繼承關系圖來看下:通過上圖以及前面說的LifeCyecle的模板方法模式,我們知道StandardEngine的初始化鉤子方法initInternal方法最終調用了ContainerBase的initInternal方法,那我們拿ContainerBase#initInternal方法的代碼看看:
org.apache.catalina.core.ContainerBase#initInternal
protected void initInternal() throws LifecycleException {BlockingQueue<Runnable> startStopQueue =new LinkedBlockingQueue<Runnable>();startStopExecutor = new ThreadPoolExecutor(getStartStopThreadsInternal(),getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,startStopQueue,new StartStopThreadFactory(getName() + "-startStop-"));startStopExecutor.allowCoreThreadTimeOut(true);super.initInternal(); }我們可以看到StandardEngine的初始化僅僅是創建了一個ThreadPoolExecutor,當看到這里的時候,筆者當時也納悶了,StandardEngine#init竟然沒有調用StandardHost#init方法,那么StandardHost的init方法是什么時候被調用的呢?遇到這種不知道到底方法怎么調用的時候怎么辦呢?筆者介紹個方法給大家。我們現在需要知道StandardHost#init方法何時被調用的,而我們知道init最終會調用鉤子的initInternal方法,因此這個時候,我們可以在StandardHost中override initInternal方法,增加了實現方法以后,有兩種方法可以用,一種就是設置個斷點debug一下就可以看出線程調用棧了,另外一種就是在新增的方法中打印出調用棧。筆者這里采用第二種方法,我們增加如下的initInternal方法到StandardHost中:
org.apache.catalina.core.StandardHost#initInternal
protected void initInternal() throws LifecycleException {Throwable ex = new Throwable();StackTraceElement[] stackElements = ex.getStackTrace();if (stackElements != null) {for (int i = stackElements.length - 1; i >= 0; i--) {System.out.print(stackElements[i].getClassName() + "\t");System.out.print(stackElements[i].getMethodName() + "\t");System.out.print(stackElements[i].getFileName() + "\t");System.out.println(stackElements[i].getLineNumber());}}super.initInternal(); }上面的代碼將會打印出方法調用堆棧,對于調試非常有用,上面的方法運行以后在控制臺打印出了如下的堆棧信息:
stack info
java.lang.Thread run Thread.java 680 java.util.concurrent.ThreadPoolExecutor$Worker run ThreadPoolExecutor.java 918 java.util.concurrent.ThreadPoolExecutor$Worker runTask ThreadPoolExecutor.java 895 java.util.concurrent.FutureTask run FutureTask.java 138 java.util.concurrent.FutureTask$Sync innerRun FutureTask.java 303 org.apache.catalina.core.ContainerBase$StartChild call ContainerBase.java 1549 org.apache.catalina.core.ContainerBase$StartChild call ContainerBase.java 1559 org.apache.catalina.util.LifecycleBase start LifecycleBase.java 139 org.apache.catalina.util.LifecycleBase init LifecycleBase.java 102 org.apache.catalina.core.StandardHost initInternal StandardHost.java 794通過控制臺的信息,我們看到是StartChild#call方法調用的,而我們查看StartChild#call方法其實是在StandardEngine的startInternal方法中通過異步線程池去初始化子容器。因此到這里我們就理清楚了,StarndardHost的init方法是在調用start方法的時候被初始化。那么接下來我們就來看看,start方法的整體調用流程。
BootStrap#start
采用分析load方法一樣的方法,經過對BootStrap#start的分析,我們最終可以得到得到如下的調用鏈:
org.apache.catalina.startup.Bootstrap#start call stack
org.apache.catalina.startup.Bootstrap#start ->org.apache.catalina.startup.Catalina#start 通過反射調用 -->org.apache.catalina.core.StandardServer#start --->org.apache.catalina.core.StandardService#start ---->org.apache.catalina.core.StandardEngine#start ---->org.apache.catalina.Executor#start ---->org.apache.catalina.connector.Connector#start綜合上文的描述我們總體得到如下的調用鏈:
org.apache.catalina.startup.Bootstrap#main call stack
org.apache.catalina.startup.Bootstrap#main ->org.apache.catalina.startup.Bootstrap#init ->org.apache.catalina.startup.Bootstrap#load -->org.apache.catalina.startup.Catalina#load --->org.apache.catalina.core.StandardServer#init ---->org.apache.catalina.core.StandardService#init ----->org.apache.catalina.connector.Connector#init ----->org.apache.catalina.core.StandardEngine#init ->org.apache.catalina.startup.Bootstrap#start -->org.apache.catalina.startup.Catalina#start 通過反射調用 --->org.apache.catalina.core.StandardServer#start ---->org.apache.catalina.core.StandardService#start ----->org.apache.catalina.core.StandardEngine#start ----->org.apache.catalina.Executor#start ----->org.apache.catalina.connector.Connector#start通過上面的分析我們已經搞清楚了Tomcat啟動的總體的過程,但是有一些關鍵的步驟,我們還需要進行進一步的深入探究。let’s do it.
?
?
?
?
Reference
Tomcat啟動過程(Tomcat源代碼閱讀系列之三)
轉載于:https://www.cnblogs.com/549294286/p/3717714.html
總結
以上是生活随笔為你收集整理的【转】Tomcat7启动的总过程 (有时间自己写下tomcat8的)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PL/SQL Developer连接本地
- 下一篇: java环境变量详解---找不到或无法加