1. 程式人生 > >Tomcat啟動流程簡析

Tomcat啟動流程簡析

Tomcat是一款我們平時開發過程中最常用到的Servlet容器。本系列部落格會記錄Tomcat的整體架構、主要元件、IO執行緒模型、請求在Tomcat內部的流轉過程以及一些Tomcat調優的相關知識。 力求達到以下幾個目的: - 更加熟悉Tomcat的工作機制,工作中遇到Tomcat相關問題能夠快速定位,從源頭來解決; - 對於一些高併發場景能夠對Tomcat進行調優; - 通過對Tomcat原始碼的分析,吸收一些Tomcat的設計的理念,應用到自己的軟體開發過程中。 ------------- ## 1. Bootstrap啟動入口 在前面分析[Tomcat啟動指令碼](https://www.cnblogs.com/54chensongxia/p/13234398.html)的過程中,我們最後發現startup.bat最後是通過呼叫Bootstrap這個類的main方法來啟動Tomcat的,所以先去看下Bootstrap這個類。 ```java public static void main(String args[]) { synchronized (daemonLock) { if (daemon == null) { // Don't set daemon until init() has completed Bootstrap bootstrap = new Bootstrap(); try { //建立Bootstrap物件,代用init方法 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")) { //一般情況下會進入這步,呼叫Bootstrap物件的load和start方法。 //將Catalina啟動設定成block模式 daemon.setAwait(true); daemon.load(args); daemon.start(); if (null == daemon.getServer()) { System.exit(1); } } 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); } } ``` 上面的程式碼邏輯比較簡單,如果我們正常啟動tomcat,會順序執行Bootstrap物件的init()方法, daemon.setAwait(true)、daemon.load(args)和daemon.start()方法。我們先看下Bootstrap物件的init方法: ```java public void init() throws Exception { initClassLoaders(); Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader); // Load our startup class and call its process() method if (log.isDebugEnabled()) log.debug("Loading startup class"); Class startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.getConstructor().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; } ``` 這個方法主要做了以下幾件事: - 建立commonLoader、catalinaLoader、sharedLoader類載入器(預設情況下這三個類載入器指向同一個物件。建議看看createClassLoader方法,裡面做的事情還挺多,比如裝載catalina.properties裡配置的目錄下的檔案和jar包,後兩個載入器的父載入器都是第一個,最後註冊了MBean,可以用於JVM監控該物件); - 例項化一個org.apache.catalina.startup.Catalina物件,並賦值給靜態成員catalinaDaemon,以sharedLoader作為入參通過反射呼叫該物件的setParentClassLoader方法。 執行完init()方法,就開始執行bootstrap物件的load和start方法; ```java 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); if (log.isDebugEnabled()) log.debug("Calling startup class " + method); method.invoke(catalinaDaemon, param); } ``` 呼叫catalinaDaemon物件的load方法,catalinaDaemon這個物件的型別是org.apache.catalina.startup.Catalina。strat方法也是類似的,最後都是呼叫Catalina的start方法。 **總結下Bootstrap的啟動方法最主要乾的事情就是建立了Catalina物件,並呼叫它的load和start方法。** ## 2. Catalina的load和start方法 第一節分析到Bootstrap會觸發呼叫Catalina的load和start方法。 ```java /** * 從註釋可以看出這個方法的作用是建立一個Server例項 * Start a new server instance. */ public void load() { if (loaded) { return; } loaded = true; long t1 = System.nanoTime(); //檢查臨時目錄 initDirs(); // Before digester - it may be needed initNaming(); // Create and execute our Digester Digester digester = createStartDigester(); InputSource inputSource = null; InputStream inputStream = null; File file = null; try { 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); } } if (inputStream == null) { try { inputStream = getClass().getClassLoader() .getResourceAsStream(getConfigFile()); inputSource = new InputSource (getClass().getClassLoader() .getResource(getConfigFile()).toString()); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(sm.getString("catalina.configFail", getConfigFile()), e); } } } // This should be included in catalina.jar // Alternative: don't bother with xml, just create it manually. if (inputStream == null) { try { inputStream = getClass().getClassLoader() .getResourceAsStream("server-embed.xml"); inputSource = new InputSource (getClass().getClassLoader() .getResource("server-embed.xml").toString()); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(sm.getString("catalina.configFail", "server-embed.xml"), e); } } } if (inputStream == null || inputSource == null) { if (file == null) { log.warn(sm.getString("catalina.configFail", getConfigFile() + "] or [server-embed.xml]")); } else { log.warn(sm.getString("catalina.configFail", file.getAbsolutePath())); if (file.exists() && !file.canRead()) { log.warn("Permissions incorrect, read permission is not allowed on the file."); } } return; } 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 { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { // Ignore } } } getServer().setCatalina(this); getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile()); getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile()); // Stream redirection initStreams(); // Start the new server try { 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); } } long t2 = System.nanoTime(); if(log.isInfoEnabled()) { log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms"); } } ``` 將上面的程式碼精簡下: ```java Digester digester = createStartDigester(); inputSource.setByteStream(inputStream); digester.push(this); digester.parse(inputSource); getServer().setCatalina(this); getServer().init(); ``` 做的事情就兩個: - 建立一個Digester物件(Digester物件的作用就是解析server.xml配置檔案,這邊會先載入conf/server.xml檔案,找不到的話會嘗試載入server-embed.xml這個配置檔案),解析完成後生成org.apache.catalina.core.StandardServer、org.apache.catalina.core.StandardService、org.apache.catalina.connector.Connector、org.apache.catalina.core.StandardEngine、org.apache.catalina.core.StandardHost、org.apache.catalina.core.StandardContext等等一系列物件,這些物件從前到後前一個包含後一個物件的引用(一對一或一對多的關係)。最後將StandardServer賦值給Catalina物件的server屬性;如果你配置了聯結器元件共享的執行緒池,還會生成StandardThreadExecutor物件。 - 第二件事就是呼叫StandardServer的init方法。 **臨時總結下**:Catalina的load方法的作用主要是解析conf/server.xml,生成StandardServer物件,再觸發StandardServer的init方法。 第一節中還分析到Bootstrap會觸發呼叫Catalina的start方法。那麼我們看看start方法中幹了什麼。 ```java /** * Start a new server instance. */ public void start() { if (getServer() == null) { load(); } 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(); } 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(); } 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(); stop(); } } ``` 這段程式碼最主要的作用就是呼叫StandardServer物件的start方法。 **總結下:Catalina物件的laod和start方法的作用是解析conf/server.xml,生成StandardServer物件,再觸發StandardServer的init方法和start方法。** 到這邊為止我們可以看到Tomcat的啟動流程還是很清晰的,下面繼續看StandardServer的init方法和start到底幹了些什麼。 ## 3.StandardServer的init和start方法 通過尋找StandardServer的init方法,我們發現StandardServer本身沒有實現這個方法,這個方法是它從父類LifecycleBase中繼承過來的: ```java @Override public final synchronized void init() throws LifecycleException { if (!state.equals(LifecycleState.NEW)) { invalidTransition(Lifecycle.BEFORE_INIT_EVENT); } try { //釋出初始化容器時間,對應的listener做相應處理 setStateInternal(LifecycleState.INITIALIZING, null, false); //呼叫子類的initInternal() initInternal(); //釋出容器已經初始化事件,對應的listener做相應處理 setStateInternal(LifecycleState.INITIALIZED, null, false); } catch (Throwable t) { handleSubClassException(t, "lifecycleBase.initFail", toString()); } } ``` 所以呼叫StandardServer的init方法,其實是促發了容器初始化事件釋出,然後又調到了StandardServer的initInternal方法。那麼我們看看StandardServer的start方法的邏輯是什麼。 程式碼點進去,發現StandardServer的start方法也是調的父類LifecycleBase中的方法。 ```java @Override public final synchronized void start() throws LifecycleException { if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) || LifecycleState.STARTED.equals(state)) { if (log.isDebugEnabled()) { Exception e = new LifecycleException(); log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e); } else if (log.isInfoEnabled()) { log.info(sm.getString("lifecycleBase.alreadyStarted", toString())); } return; } if (state.equals(LifecycleState.NEW)) { init(); } else if (state.equals(LifecycleState.FAILED)) { stop(); } else if (!state.equals(LifecycleState.INITIALIZED) && !state.equals(LifecycleState.STOPPED)) { invalidTransition(Lifecycle.BEFORE_START_EVENT); } try { //釋出事件 setStateInternal(LifecycleState.STARTING_PREP, null, false); //呼叫子類的startInternal startInternal(); if (state.equals(LifecycleState.FAILED)) { // This is a 'controlled' failure. The component put itself into the // FAILED state so call stop() to complete the clean-up. stop(); } else if (!state.equals(LifecycleState.STARTING)) { // Shouldn't be necessary but acts as a check that sub-classes are // doing what they are supposed to. invalidTransition(Lifecycle.AFTER_START_EVENT); } else { //釋出容器啟動事件 setStateInternal(LifecycleState.STARTED, null, false); } } catch (Throwable t) { // This is an 'uncontrolled' failure so put the component into the // FAILED state and throw an exception. handleSubClassException(t, "lifecycleBase.startFail", toString()); } } ``` 從以上init和start方法的定義可以看到這兩個方法最終將會呼叫StandardServer中定義的initInternal和startInternal。 先來看initInternal方法 ```java protected void initInternal() throws LifecycleException { super.initInternal(); // Register global String cache // Note although the cache is global, if there are multiple Servers // present in the JVM (may happen when embedding) then the same cache // will be registered under multiple names onameStringCache = register(new StringCache(), "type=StringCache"); // Register the MBeanFactory MBeanFactory factory = new MBeanFactory(); factory.setContainer(this); onameMBeanFactory = register(factory, "type=MBeanFactory"); // Register the naming resources globalNamingResources.init(); // Populate the extension validator with JARs from common and shared // class loaders if (getCatalina() != null) { ClassLoader cl = getCatalina().getParentClassLoader(); // Walk the class loader hierarchy. Stop at the system class loader. // This will add the shared (if present) and common class loaders while (cl != null && cl != ClassLoader.getSystemClassLoader()) { if (cl instanceof URLClassLoader) { URL[] urls = ((URLClassLoader) cl).getURLs(); for (URL url : urls) { if (url.getProtocol().equals("file")) { try { File f = new File (url.toURI()); if (f.isFile() && f.getName().endsWith(".jar")) { ExtensionValidator.addSystemResource(f); } } catch (URISyntaxException e) { // Ignore } catch (IOException e) { // Ignore } } } } cl = cl.getParent(); } } // Initialize our defined Services for (int i = 0; i < services.length; i++) { services[i].init(); } } ``` 重點程式碼在最後,迴圈呼叫了Service元件的init方法。 再來看StandardServer的startInternal方法 ```java @Override protected void startInternal() throws LifecycleException { fireLifecycleEvent(CONFIGURE_START_EVENT, null); setState(LifecycleState.STARTING); globalNamingResources.start(); // Start our defined Services synchronized (servicesLock) { for (int i = 0; i < services.length; i++) { services[i].start(); } } } ``` 也是迴圈呼叫了Service元件的start方法。這邊的Service元件就是在從conf/server.xml中解析出來的StandardService物件,檢視下這個類的繼承體系: ```java LifecycleBase (org.apache.catalina.util) LifecycleMBeanBase (org.apache.catalina.util) StandardService (org.apache.catalina.core) ``` 我們發現這個類繼承體系和StandardServer是一樣的。其實我們再觀察的仔細一點會發現從conf/server.xml解析胡來的類的繼承體系都是一樣的。所以我們呼叫這些類的init和start方法最後還是會呼叫到他們的initInternal和startInternal方法。 ## 4. StandardService的initInternal和startInternal方法 先看下StandardService的initInternal方法 ```java @Override protected void initInternal() throws LifecycleException { super.initInternal(); //呼叫engine的initInternal方法,這個方法中也沒做特別重要的操作,只是做了一個getReal操作 if (engine != null) { engine.init(); } //StandardThreadExecutor的initInternal方法中沒沒幹什麼事情 // Initialize any Executors for (Executor executor : findExecutors()) { if (executor instanceof JmxEnabled) { ((JmxEnabled) executor).setDomain(getDomain()); } executor.init(); } // Initialize mapper listener //這步也沒做什麼重要操作 mapperListener.init(); // Initialize our defined Connectors // 聯結器主鍵的初始化,主要是檢查聯結器的protocolHandler的主鍵,並將其初始化. synchronized (connectorsLock) { for (Connector connector : connectors) { connector.init(); } } } ``` 看了上面的程式碼,覺得Tomcat原始碼邏輯還是很清晰的。之前在分析Tomcat元件的文章中講到Service元件是有Connector元件、engine元件和一個可選的執行緒池組成。上面的程式碼中正好對應了這三個元件的初始化話。 Connector元件和engine元件的初始化又會觸發他們各自子元件的初始化,所以StandardService的initInternal方法會觸發Tomcat下各類元件的初始化。這邊大致記錄下各個元件初始化話的順序: - engine元件初始化:engine元件初始化沒做什麼特別的操作,也沒觸發它的子元件(Host、Context和Wrapper元件的初始化),所以這步比較簡單; - Executor元件的初始化:沒有觸發其他元件初始化; - Mapper元件初始化:mapper元件初始化也沒幹什麼重要的操作,也沒觸發其他子元件初始化; - Connector元件初始化:檢查聯結器的protocolHandler的子元件,並**觸發其初始化**; - ProtocolHandler元件初始化:**觸發Endpoint元件初始化**,Endpoint類才是接收轉化請求的真正的類; 然後再看StandardService的startInternal方法 ```java protected void startInternal() throws LifecycleException { if(log.isInfoEnabled()) log.info(sm.getString("standardService.start.name", this.name)); setState(LifecycleState.STARTING); // Start our defined Container first if (engine != null) { synchronized (engine) { engine.start(); } } synchronized (executors) { for (Executor executor: executors) { executor.start(); } } mapperListener.start(); // Start our defined Connectors second synchronized (connectorsLock) { for (Connector connector: connectors) { // If it has already failed, don't try and start it if (connector.getState() != LifecycleState.FAILED) { connector.start(); } } } } ``` 邏輯依然很清楚,StandardService會依次觸發各個子元件的start方法。 - Engine元件的start:Engine元件的start方法組要作用還是觸發了Host元件的start方法,具體程式碼見 ```java protected synchronized void startInternal() throws LifecycleException { // Start our subordinate components, if any if ((loader != null) && (loader instanceof Lifecycle)) ((Lifecycle) loader).start(); logger = null; getLogger(); if ((manager != null) && (manager instanceof Lifecycle)) ((Lifecycle) manager).start(); if ((cluster != null) && (cluster instanceof Lifecycle)) ((Lifecycle) cluster).start(); Realm realm = getRealmInternal(); if ((realm != null) && (realm instanceof Lifecycle)) ((Lifecycle) realm).start(); if ((resources != null) && (resources instanceof Lifecycle)) ((Lifecycle) resources).start(); // 找出Engine的子容器,也就是Host容器 Container children[] = findChildren(); List> results = new ArrayList>(); //利用執行緒池呼叫Host的start方法 for (int i = 0; i < children.length; i++) { results.add(startStopExecutor.submit(new StartChild(children[i]))); } boolean fail = false; for (Future result : results) { try { result.get(); } catch (Exception e) { log.error(sm.getString("containerBase.threadedStartFailed"), e); fail = true; } } if (fail) { throw new LifecycleException( sm.getString("containerBase.threadedStartFailed")); } // Start the Valves in our pipeline (including the basic), if any if (pipeline instanceof Lifecycle) ((Lifecycle) pipeline).start(); setState(LifecycleState.STARTING); // Start our thread threadStart(); } ``` - Host元件的start:經過前面介紹,我們知道Host元件的start方法最後還是會呼叫自己startInternal方法; - Context元件的start:觸發Wrapper的start,載入filter、Servlet等; - Wrapper元件的start: 這邊我們重點看下StandardContext的startInternal,這個方法乾的事情比較多: ```java protected synchronized void startInternal() throws LifecycleException { if(log.isDebugEnabled()) log.debug("Starting " + getBaseName()); // Send j2ee.state.starting notification if (this.getObjectName() != null) { Notification notification = new Notification("j2ee.state.starting", this.getObjectName(), sequenceNumber.getAndIncrement()); broadcaster.sendNotification(notification); } setConfigured(false); boolean ok = true; // Currently this is effectively a NO-OP but needs to be called to // ensure the NamingResources follows the correct lifecycle if (namingResources != null) { namingResources.start(); } // Post work directory postWorkDirectory(); // Add missing components as necessary if (getResources() == null) { // (1) Required by Loader if (log.isDebugEnabled()) log.debug("Configuring default Resources"); try { setResources(new StandardRoot(this)); } catch (IllegalArgumentException e) { log.error(sm.getString("standardContext.resourcesInit"), e); ok = false; } } if (ok) { resourcesStart(); } if (getLoader() == null) { WebappLoader webappLoader = new WebappLoader(getParentClassLoader()); webappLoader.setDelegate(getDelegate()); setLoader(webappLoader); } // An explicit cookie processor hasn't been specified; use the default if (cookieProcessor == null) { cookieProcessor = new Rfc6265CookieProcessor(); } // Initialize character set mapper getCharsetMapper(); // Validate required extensions boolean dependencyCheck = true; try { dependencyCheck = ExtensionValidator.validateApplication (getResources(), this); } catch (IOException ioe) { log.error(sm.getString("standardContext.extensionValidationError"), ioe); dependencyCheck = false; } if (!dependencyCheck) { // do not make application available if dependency check fails ok = false; } // Reading the "catalina.useNaming" environment variable String useNamingProperty = System.getProperty("catalina.useNaming"); if ((useNamingProperty != null) && (useNamingProperty.equals("false"))) { useNaming = false; } if (ok && isUseNaming()) { if (getNamingContextListener() == null) { NamingContextListener ncl = new NamingContextListener(); ncl.setName(getNamingContextName()); ncl.setExceptionOnFailedWrite(getJndiExceptionOnFailedWrite()); addLifecycleListener(ncl); setNamingContextListener(ncl); } } // Standard container startup if (log.isDebugEnabled()) log.debug("Processing standard container startup"); // Binding thread ClassLoader oldCCL = bindThread(); try { if (ok) { // Start our subordinate components, if any Loader loader = getLoader(); if (loader instanceof Lifecycle) { ((Lifecycle) loader).start(); } // since the loader just started, the webapp classloader is now // created. setClassLoaderProperty("clearReferencesRmiTargets", getClearReferencesRmiTargets()); setClassLoaderProperty("clearReferencesStopThreads", getClearReferencesStopThreads()); setClassLoaderProperty("clearReferencesStopTimerThreads", getClearReferencesStopTimerThreads()); setClassLoaderProperty("clearReferencesHttpClientKeepAliveThread", getClearReferencesHttpClientKeepAliveThread()); setClassLoaderProperty("clearReferencesObjectStreamClassCaches", getClearReferencesObjectStreamClassCaches()); setClassLoaderProperty("skipMemoryLeakChecksOnJvmShutdown", getSkipMemoryLeakChecksOnJvmShutdown()); // By calling unbindThread and bindThread in a row, we setup the // current Thread CCL to be the webapp classloader unbindThread(oldCCL); oldCCL = bindThread(); // Initialize logger again. Other components might have used it // too early, so it should be reset. logger = null; getLogger(); Realm realm = getRealmInternal(); if(null != realm) { if (realm instanceof Lifecycle) { ((Lifecycle) realm).start(); } // Place the CredentialHandler into the ServletContext so // applications can have access to it. Wrap it in a "safe" // handler so application's can't modify it. CredentialHandler safeHandler = new CredentialHandler() { @Override public boolean matches(String inputCredentials, String storedCredentials) { return getRealmInternal().getCredentialHandler().matches(inputCredentials, storedCredentials); } @Override public String mutate(String inputCredentials) { return getRealmInternal().getCredentialHandler().mutate(inputCredentials); } }; context.setAttribute(Globals.CREDENTIAL_HANDLER, safeHandler); } // Notify our interested LifecycleListeners fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null); // Start our child containers, if not already started for (Container child : findChildren()) { if (!child.getState().isAvailable()) { child.start(); } } // Start the Valves in our pipeline (including the basic), // if any if (pipeline instanceof Lifecycle) { ((Lifecycle) pipeline).start(); } // Acquire clustered manager Manager contextManager = null; Manager manager = getManager(); if (manager == null) { if (log.isDebugEnabled()) { log.debug(sm.getString("standardContext.cluster.noManager", Boolean.valueOf((getCluster() != null)), Boolean.valueOf(distributable))); } if ( (getCluster() != null) && distributable) { try { contextManager = getCluster().createManager(getName()); } catch (Exception ex) { log.error("standardContext.clusterFail", ex); ok = false; } } else { contextManager = new StandardManager(); } } // Configure default manager if none was specified if (contextManager != null) { if (log.isDebugEnabled()) { log.debug(sm.getString("standardContext.manager", contextManager.getClass().getName())); } setManager(contextManager); } if (manager!=null && (getCluster() != null) && distributable) { //let the cluster know that there is a context that is distributable //and that it has its own manager getCluster().registerManager(manager); } } if (!getConfigured()) { log.error(sm.getString("standardContext.configurationFail")); ok = false; } // We put the resources into the servlet context if (ok) getServletContext().setAttribute (Globals.RESOURCES_ATTR, getResources()); if (ok ) { if (getInstanceManager() == null) { javax.naming.Context context = null; if (isUseNaming() && getNamingContextListener() != null) { context = getNamingContextListener().getEnvContext(); } Map> injectionMap = buildInjectionMap( getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources()); setInstanceManager(new DefaultInstanceManager(context, injectionMap, this, this.getClass().getClassLoader())); } getServletContext().setAttribute( InstanceManager.class.getName(), getInstanceManager()); InstanceManagerBindings.bind(getLoader().getClassLoader(), getInstanceManager()); } // Create context attributes that will be required if (ok) { getServletContext().setAttribute( JarScanner.class.getName(), getJarScanner()); } // Set up the context init params mergeParameters(); // Call ServletContainerInitializers for (Map.Entry>> entry : initializers.entrySet()) { try { entry.getKey().onStartup(entry.getValue(), getServletContext()); } catch (ServletException e) { log.error(sm.getString("standardContext.sciFail"), e); ok = false; break; } } // Configure and call application event listeners if (ok) { if (!listenerStart()) { log.error(sm.getString("standardContext.listenerFail")); ok = false; } } // Check constraints for uncovered HTTP methods // Needs to be after SCIs and listeners as they may programmatically // change constraints if (ok) { checkConstraintsForUncoveredMethods(findConstraints()); } try { // Start manager Manager manager = getManager(); if (manager instanceof Lifecycle) { ((Lifecycle) manager).start(); } } catch(Exception e) { log.error(sm.getString("standardContext.managerFail"), e); ok = false; } // Configure and call application filters if (ok) { if (!filterStart()) { log.error(sm.getString("standardContext.filterFail")); ok = false; } } // Load and initialize all "load on startup" servlets if (ok) { if (!loadOnStartup(findChildren())){ log.error(sm.getString("standardContext.servletFail")); ok = false; } } // Start ContainerBackgroundProcessor thread super.threadStart(); } finally { // Unbinding thread unbindThread(oldCCL); } // Set available status depending upon startup success if (ok) { if (log.isDebugEnabled()) log.debug("Starting completed"); } else { log.error(sm.getString("standardContext.startFailed", getName())); } startTime=System.currentTimeMillis(); // Send j2ee.state.running notification if (ok && (this.getObjectName() != null)) { Notification notification = new Notification("j2ee.state.running", this.getObjectName(), sequenceNumber.getAndIncrement()); broadcaster.sendNotification(notification); } // The WebResources implementation caches references to JAR files. On // some platforms these references may lock the JAR files. Since web // application start is likely to have read from lots of JARs, trigger // a clean-up now. getResources().gc(); // Reinitializing if something went wrong if (!ok) { setState(LifecycleState.FAILED); } else { setState(LifecycleState.STARTING); } } ``` 上面的程式碼有4處重點:呼叫ServletContainerInitializers、啟用Listener、啟用Filter和啟用startup的Servlet。這個和我們平時對Tomcat啟動流程的認知是一致的。 到這裡整個Container元件(包括Engine、Host、Context和Wrapper元件)的start方法呼叫就結束了。接下來是Connector和Mapper元件的start。 ```java //MapperListenner的startInternal public void startInternal() throws LifecycleException { setState(LifecycleState.STARTING); Engine engine = service.getContainer(); if (engine == null) { return; } findDefaultHost(); addListeners(engine); Container[] conHosts = engine.findChildren(); for (Container conHost : conHosts) { Host host = (Host) conHost; if (!LifecycleState.NEW.equals(host.getState())) { // Registering the host will register the context and wrappers registerHost(host); } } } ``` 以上方法的主要作用是將Host元件和域名對映起來。 最後看下Connector元件的start: ```java protected void startInternal() throws LifecycleException { // Validate settings before starting if (getPortWithOffset() < 0) { throw new LifecycleException(sm.getString( "coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset()))); } setState(LifecycleState.STARTING); try { //促發protocolHandler元件的start,最後促發endpoint元件的start //觸發endpoint時會建立exceutor執行緒池,預設的話核心執行緒數10,最大執行緒數200 //建立poller執行緒,最大是2個執行緒,如果你機器cpu的核數小於2的話就建立1個 //建立accetpor執行緒,預設是1個(可以看看Acceptor這個類的原始碼,瞭解下怎麼接收請求的) protocolHandler.start(); } catch (Exception e) { throw new LifecycleException( sm.getString("coyoteConnector.protocolHandlerStartFailed"), e); } } ``` 通過以上一些列複雜的呼叫過程,最終執行完所有在server.xml裡配置的節點的實現類中initInternal和startInternal方法。上面提到的org.apache.catalina.core.StandardServer、org.apache.catalina.core.StandardService、org.apache.catalina.connector.Connector、org.apache.catalina.core.StandardEngine、org.apache.catalina.core.StandardHost、org.apache.catalina.core.StandardContext等等元件的這兩個方法都會呼叫到。 至此,Tomcat已經能開始響應瀏覽器發過來的請求了。至於具體的Tomcat響應請求流程會在後續部落格中介紹。 ## 5. 總結 看了整個啟動流程,雖然邏輯是比較清楚的,但是流程比較上,所以有必要做下總結: - step1:Bootstrap作為整個Tomcat主啟動類,最主要的功能是建立Catalina物件,並呼叫它的load和start方法; - step2:Catalina的load方法的作用主要是解析conf/server.xml,生成StandardServer物件(此時生成StandardServer物件中已經包含了各種子元件,比如StandardService、StandardEngine等),再觸發StandardServer的init方法;Catalina的start方法又觸發了StandardServer的start方法; - step3:StandardServer的init方法和start方法會依次觸發各個子元件的initInternal和startInternal方法。大致的觸發順序是: Engine元件的initInternal(這邊要注意的是Engine元件並沒有觸發它的子元件Host、Context和Wrapper的initInternal)-->
Executor元件initInternal(處理請求的工作執行緒池)-->Mapper元件初始化(mapper元件初始化也沒幹什麼重要的操作,也沒觸發其他子元件初始化)-->Connector元件初始化(檢查聯結器的protocolHandler的子元件,並**觸發其初始化**)-->ProtocolHandler元件初始化(**觸發Endpoint元件初始化**)​ ​ Engine元件的startInternal(主要作用是觸發Host元件的start)-->Host元件的startInternal(主要作用是觸發Context元件的startInternal)-->Contextz元件的startInternal(載入呼叫ServletContainerInitializers、載入Listener、載入filtr和startup的Servlet,並且觸發Wrapper元件的startInternal)-->
Wrapper元件的startInternal(載入對映Servlet)-->Mapper元件的startInternal(將域名和Host元件對映起來)-->Connector元件的startInternal(protocolHandler元件的start,最後促發endpoint元件的start) ![](https://img2020.cnblogs.com/blog/1775037/202007/1775037-20200704220716365-1370663428.png) ## 參考 - [Tomcat詳細知識點](https://blog.csdn.net/m0_38060977/article/details/104100839) - [Tomcat知識貼彙總](https://blog.csdn.net/m0_38060977/category_96864