tomcat8.5.57原始碼閱讀筆記4 - daemon.start()
Bootstrap#start()
daemon = bootstrap, 所以呼叫的還是org.apache.catalina.startup.Bootstrap#start()
public void start() throws Exception { if (catalinaDaemon == null) { init(); } Method method = catalinaDaemon.getClass().getMethod("start", (Class[]) null); // 反射呼叫 Catalina#start() method.invoke(catalinaDaemon, (Object[]) null); }
反射呼叫了 Catalina#start() 方法, 進行啟動工作.
Catalina#start()
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 { //StandardServer#start(), 最終呼叫的是 StandardServer#startInternal() 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); } } // Bootstrap中會設定await為true,其目的在於讓tomcat在shutdown埠阻塞監聽關閉命令 if (await) { await(); stop(); } }
這裡繼續呼叫了 Server 的 start 方法. 實際呼叫的是 org.apache.catalina.util.LifecycleBase#start(),
然後此方法中呼叫了抽象方法startInternal(), 也就是 StandardServer#startInternal()
StandardServer#startInternal()
protected void startInternal() throws LifecycleException { // 事件通知 CONFIGURE_START_EVENT -> configure_start fireLifecycleEvent(CONFIGURE_START_EVENT, null); // 修改tomcat狀態為 STARTING, 這一步也會進行事件通知 START_EVENT -> start setState(LifecycleState.STARTING); //NamingResourcesImpl#startInternal() 全域性資源 //在server.xml中配置了該變數, pathname="conf/tomcat-users.xml" globalNamingResources.start(); // Start our defined Services synchronized (servicesLock) { for (Service service : services) { // StandardService.startInternal() service.start(); } } }
service.start() 最終呼叫的是 StandardService.startInternal()
StandardService.startInternal()
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) { //StandardEngine#start() 最終呼叫 StandardEngine#startInternal() //這一句程式碼, 就將 Host, Context, Wrapper 的 start 執行到了 //host是通過 server.xml 解析得到 //context是通過掃描檔案的方式解析得到, 有三種方式: xml配置方式, war包方式, 資料夾方式 //wrapper也是通過掃描jar包的方式得到 engine.start(); } } // 啟動Executor執行緒池, 預設情況下, 是空, 通過修改 server.xml中的tomcatThreadPool來改變 synchronized (executors) { for (Executor executor: executors) { executor.start(); } } // 啟動 MapperListener, 進行註冊功能 // 最終會呼叫 MapperListener#startInternal() 方法 //* 這個方法主要乾了兩件事情 //* 1. 將 MapperListener 註冊到容器和子容器(Host, Context, Wrapper)的 listeners 和 lifecycleListeners 中 //* 2. 註冊 host, context, wrapper mapperListener.start(); // Start our defined Connectors second synchronized (connectorsLock) { for (Connector connector: connectors) { try { // If it has already failed, don't try and start it if (connector.getState() != LifecycleState.FAILED) { //最終呼叫 Connector.startInternal() connector.start(); } } catch (Exception e) { log.error(sm.getString( "standardService.connector.startFailed", connector), e); } } } }
這裡和初始化的時候很像, 初始化的時候, 是對他們分別初始化, 而這裡, 是對他們分別啟動
1. engine.start() : 啟動 engine, 裡面會啟動 Host, Context, Wrapper. 最終會呼叫 StandardEngine#startInternal()
2. executor.start() : 啟動執行緒池, 預設情況下, 這個執行緒池為空.
3. mapperListener.start() : 將MapperListener註冊到 Host,Context,Wrapper 的監聽器中, 然後對 Host, Context, Wrapper 進行對映
4. connector.start() : 啟動聯結器
StandardEngine#startInternal()
protected synchronized void startInternal() throws LifecycleException { // Log our server identification information if(log.isInfoEnabled()) log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo()); // Standard container startup //通過解析 server.xml 檔案, 可以拿到 engine下面有一個 host, 也就是說, StandardEngine.children有一個host super.startInternal(); }
super.startInternal() 呼叫的是父類org.apache.catalina.core.ContainerBase#startInternal() 方法:
protected synchronized void startInternal() throws LifecycleException { // Start our subordinate components, if any logger = null; getLogger(); //叢集客戶端 Cluster cluster = getClusterInternal(); if (cluster instanceof Lifecycle) { //如果有叢集的話, 則會啟動叢集 ((Lifecycle) cluster).start(); } //域 Realm realm = getRealmInternal(); if (realm instanceof Lifecycle) { ((Lifecycle) realm).start(); } // Start our child containers, if any // 把子容器的啟動步驟放線上程中處理,預設情況下執行緒池只有一個執行緒處理任務佇列 //StandardEngine 呼叫的時候, 這裡拿到的 children 有一個值: StandardHost[localhost] // <Engine name="Catalina" defaultHost="localhost"> // <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true" startStopThreads="1"> // </engine> Container children[] = findChildren(); List<Future<Void>> results = new ArrayList<>(); for (Container child : children) { //StartChild 的 call方法, 呼叫的是 child.start() 方法, 最終會呼叫 StandardHost.startInternal results.add(startStopExecutor.submit(new StartChild(child))); } MultiThrowable multiThrowable = null; // 阻塞當前執行緒,直到子容器start完成 for (Future<Void> result : results) { try { result.get(); } catch (Throwable e) { log.error(sm.getString("containerBase.threadedStartFailed"), e); if (multiThrowable == null) { multiThrowable = new MultiThrowable(); } multiThrowable.add(e); } } if (multiThrowable != null) { throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"), multiThrowable.getThrowable()); } // Start the Valves in our pipeline (including the basic), if any // 啟用 Pipeline --> StandardPipeline#startInternal() if (pipeline instanceof Lifecycle) { ((Lifecycle) pipeline).start(); } //StandardHost呼叫時, 激發 STARTING 監聽器 HostConfig,最終會呼叫 HostConfig#start() 方法 - 這一步很關鍵 setState(LifecycleState.STARTING); // Start our thread // 開啟ContainerBackgroundProcessor執行緒用於呼叫子容器的backgroundProcess方法, // 預設情況下backgroundProcessorDelay=-1,不會啟用該執行緒 threadStart(); }
1. 通過Future框架非同步呼叫 StandardHost#startInternal() 方法, 並阻塞等待所有當前 engine 下所有的 Host 啟動完成
2. setState() 時, 會激發監聽器 HostConfig. 這個HostConfig 是在 解析 Server.xml 的時候, 建立並繫結的.
StandardHost#startInternal()
protected synchronized void startInternal() throws LifecycleException { // Set error report valve // errorValve預設使用 ErrorReportValve String errorValve = getErrorReportValveClass(); if ((errorValve != null) && (!errorValve.equals(""))) { try { boolean found = false; // 如果所有的閥門中已經存在這個例項,則不進行處理,否則新增到 Pipeline 中 Valve[] valves = getPipeline().getValves(); for (Valve valve : valves) { if (errorValve.equals(valve.getClass().getName())) { found = true; break; } } // 如果未找到則新增到 Pipeline 中,注意是新增到 basic valve 的前面 // 預設情況下,first valve 是 AccessLogValve,basic 是 StandardHostValve if(!found) { Valve valve = (Valve) Class.forName(errorValve).getConstructor().newInstance(); getPipeline().addValve(valve); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString( "standardHost.invalidErrorReportValveClass", errorValve), t); } } // 呼叫父類 ContainerBase,完成統一的啟動動作 super.startInternal(); }
這裡的 startInternal() 就是上面的org.apache.catalina.core.ContainerBase#startInternal() 方法.
但是此時, host 的 children 是空的, 所以裡面並沒有能夠呼叫 StandardContext#startInternal()
HostConfig#lifecycleEvent()
HostConfig 實現了 LifecycleListener 介面. 所以實際上, 他是一個監聽器.
setState() 的時候, 會激發 HostConfig的監聽器方法lifecycleEvent(),
public void lifecycleEvent(LifecycleEvent event) { // Identify the host we are associated with try { host = (Host) event.getLifecycle(); if (host instanceof StandardHost) { setCopyXML(((StandardHost) host).isCopyXML()); setDeployXML(((StandardHost) host).isDeployXML()); setUnpackWARs(((StandardHost) host).isUnpackWARs()); setContextClass(((StandardHost) host).getContextClass()); } } catch (ClassCastException e) { log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e); return; } // Process the event that has occurred //判斷事件是否由 Host 發出,並且為 HostConfig 設定屬性 if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) { check(); } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) { beforeStart(); } else if (event.getType().equals(Lifecycle.START_EVENT)) { start(); } else if (event.getType().equals(Lifecycle.STOP_EVENT)) { stop(); } }
根據當前事件的狀態, 會呼叫這裡的 start() 方法. 此start 方法中, 會執行 部署 webapp 的方法 :org.apache.catalina.startup.HostConfig#deployApps()
protected void deployApps() { File appBase = host.getAppBaseFile(); File configBase = host.getConfigBaseFile(); // 過濾出 webapp 要部署應用的目錄 String[] filteredAppPaths = filterAppPaths(appBase.list()); // Deploy XML descriptors from configBase // 1. xml部署 - 不推薦這麼使用 // <host><context docBase="D://abc/eee" path="sdm" reloadable="true"></context></host> // 部署 xml 描述檔案 deployDescriptors(configBase, configBase.list()); // Deploy WARs // 2. war包部署 // 解壓 war 包,但是這裡還不會去啟動應用 deployWARs(appBase, filteredAppPaths); // Deploy expanded folders // 3. 目錄部署 // 處理已經存在的目錄,前面解壓的 war 包不會再行處理 deployDirectories(appBase, filteredAppPaths); }
原始碼裡面放的幾個 Context , 都是目錄結構的, 所以會走 deployDirectories() 方法
protected void deployDirectories(File appBase, String[] files) { if (files == null) return; ExecutorService es = host.getStartStopExecutor(); List<Future<?>> results = new ArrayList<>(); for (String file : files) { if (file.equalsIgnoreCase("META-INF")) continue; if (file.equalsIgnoreCase("WEB-INF")) continue; File dir = new File(appBase, file); if (dir.isDirectory()) { ContextName cn = new ContextName(file, false); if (isServiced(cn.getName()) || deploymentExists(cn.getName())) continue; results.add(es.submit(new DeployDirectory(this, cn, dir))); } } for (Future<?> result : results) { try { result.get(); } catch (Exception e) { log.error(sm.getString( "hostConfig.deployDir.threaded.error"), e); } } }
1. 通過除錯, 能看到:
這裡的每一個資料夾, 轉換之後, 就是一個 Context .
2. 這裡又出現了 Future . 這次提交的是 DeployDirectory, 是 HostConfig 的一個內部類. 看一下他的 run 方法:
private static class DeployDirectory implements Runnable { private HostConfig config; private ContextName cn; private File dir; public DeployDirectory(HostConfig config, ContextName cn, File dir) { this.config = config; this.cn = cn; this.dir = dir; } @Override public void run() { config.deployDirectory(cn, dir); } }
實際呼叫的, 還是 HostConfig 的方法:
protected void deployDirectory(ContextName cn, File dir) { long startTime = 0; // Deploy the application in this directory if( log.isInfoEnabled() ) { startTime = System.currentTimeMillis(); log.info(sm.getString("hostConfig.deployDir", dir.getAbsolutePath())); } Context context = null; File xml = new File(dir, Constants.ApplicationContextXml); File xmlCopy = new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml"); DeployedApplication deployedApp; boolean copyThisXml = isCopyXML(); boolean deployThisXML = isDeployThisXML(dir, cn); try { if (deployThisXML && xml.exists()) { synchronized (digesterLock) { try { //StandardContext context = (Context) digester.parse(xml); } catch (Exception e) { ...... } finally { digester.reset(); if (context == null) { context = new FailedContext(); } } } if (copyThisXml == false && context instanceof StandardContext) { // Host is using default value. Context may override it. copyThisXml = ((StandardContext) context).getCopyXML(); } if (copyThisXml) { Files.copy(xml.toPath(), xmlCopy.toPath()); context.setConfigFile(xmlCopy.toURI().toURL()); } else { context.setConfigFile(xml.toURI().toURL()); } } else if (!deployThisXML && xml.exists()) { // Block deployment as META-INF/context.xml may contain security // configuration necessary for a secure deployment. log.error(sm.getString("hostConfig.deployDescriptor.blocked", cn.getPath(), xml, xmlCopy)); context = new FailedContext(); } else { context = (Context) Class.forName(contextClass).getConstructor().newInstance(); } // 例項化 ContextConfig,作為 LifecycleListener 新增到 Context 容器中,這和 StandardHost 的套路一樣,都是使用 XXXConfig Class<?> clazz = Class.forName(host.getConfigClass()); LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance(); context.addLifecycleListener(listener); context.setName(cn.getName()); context.setPath(cn.getPath()); context.setWebappVersion(cn.getVersion()); context.setDocBase(cn.getBaseName()); // 例項化 StandardContext 之後,為 Host 新增子節點 // 這裡呼叫的是 StandardHost#addChild() host.addChild(context); } catch (Throwable t) { ...... } finally { ...... } ...... }
這裡出現了 host.addChild(context) 方法, 需要回到 StandardHost 類中去看
//org.apache.catalina.core.StandardHost#addChild public void addChild(Container child) { if (!(child instanceof Context)) throw new IllegalArgumentException (sm.getString("standardHost.notContext")); //加入一個監聽器 child.addLifecycleListener(new MemoryLeakTrackingListener()); // Avoid NPE for case where Context is defined in server.xml with only a // docBase Context context = (Context) child; if (context.getPath() == null) { ContextName cn = new ContextName(context.getDocBase(), true); context.setPath(cn.getPath()); } super.addChild(child); }
接著看父類中的 addChild()
//org.apache.catalina.core.ContainerBase#addChild public void addChild(Container child) { if (Globals.IS_SECURITY_ENABLED) { PrivilegedAction<Void> dp = new PrivilegedAddChild(child); AccessController.doPrivileged(dp); } else { addChildInternal(child); } } private void addChildInternal(Container child) { if( log.isDebugEnabled() ) log.debug("Add child " + child + " " + this); synchronized(children) { if (children.get(child.getName()) != null) throw new IllegalArgumentException("addChild: Child name '" + child.getName() + "' is not unique"); child.setParent(this); // May throw IAE children.put(child.getName(), child); } // Start child // Don't do this inside sync block - start can be a slow process and // locking the children object can cause problems elsewhere try { if ((getState().isAvailable() || LifecycleState.STARTING_PREP.equals(getState())) && startChildren) { //addChild的時候, 呼叫啟動方法, 解析Server.xml時, 不會進此方法 //StandardHost的時候, 會進此方法, 呼叫的是 StandardContext.start() -> StandardContext.startInternal() //StandardContext的時候, 會進此方法, 呼叫的是 StandardWrapper.start() -> StandardWrapper.startInternal() child.start(); } } catch (LifecycleException e) { log.error("ContainerBase.addChild: start: ", e); throw new IllegalStateException("ContainerBase.addChild: start: " + e); } finally { fireContainerEvent(ADD_CHILD_EVENT, child); } }
此時 child 是 StandardContext. 所以會呼叫
org.apache.catalina.util.LifecycleBase#start() :
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)) { //results.add(startStopExecutor.submit(new StartChild(child))); //非同步執行 StandardHost.start() 方法時, 會走這裡 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(); 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()); } }
StandardContext 就是在這個方法中進行初始化, 以及啟動的.