之前在Spring Boot啟動過程(二)提到過createEmbeddedServletContainer創建了內嵌的Servlet容器, 我用的是默認的Tomcat。
private void createEmbeddedServletContainer { EmbeddedServletContainer localContainer = this.embeddedServletContainer; ServletContext localServletContext = getServletContext; if (localContainer == null && localServletContext == null) { EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory; this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer); } else if (localServletContext != null) { try { getSelfInitializer.onStartup(localServletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources; }getEmbeddedServletContainerFactory方法中調用了ServerProperties, 從ServerProperties的實例方法customize可以看出Springboot支援三種內嵌容器的定制化配置:Tomcat、Jetty、Undertow。
這裡直接說TomcatEmbeddedServletContainerFactory的getEmbeddedServletContainer方法了, 原因在前面那篇裡說過了。 不過首先是getSelfInitializer方法先執行的:
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer { return new ServletContextInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { selfInitialize(servletContext); } }; }將初始化的ServletContextInitializer傳給了getEmbeddedServletContainer方法。 進入了getEmbeddedServletContainer方法直接就是產生實體了一個Tomcat:
Tomcat tomcat = new Tomcat;然後生成一個臨時目錄, 並tomcat.setBaseDir, setBaseDir方法的注釋說Tomcat需要一個目錄用於暫存檔案並且它應該是第一個被調用的方法;如果方法沒有被調用會使用默認的幾個位置system properties - catalina.base, catalina.home - $PWD/tomcat.$PORT, 另外/tmp從安全角度來說不建議。
接著:
Connector connector = r(this.protocol);創建Connector過程中, 靜態代碼塊:單獨抽出來寫了。
Connector實例創建好了之後tomcat.getService.addConnector(connector), getService的getServer中new了一個StandardServer, StandardServer的初始化主要是創建了globalNamingResources(globalNamingResources主要用於管理明明上下文和JDNI上下文),
然後又產生實體了個StandardService, 代碼並沒有什麼特別的:
service = new StandardService; service.setName("Tomcat"); server.addService( service )server.addService( service )這裡除了發佈了一個PropertyChangeEvent事件, 也沒做什麼特別的, 最後返回這個server。 addConnector的邏輯和上面addService沒什麼區別。 然後是customizeConnector, 這裡設置了Connector的埠、編碼等資訊, 並將“bindOnInit”和對應值false寫入了最開頭說的靜態代碼塊中的replacements集合, IntrospectionUtils.setProperty(protocolHandler, repl, value)通過反射的方法將protocolHandler實現物件的setBindOnInit存在的情況下(拼字串拼出來的)set為前面的false, 這個方法裡有大量的判斷比如參數類型及setter的參數類型, 比如返回數值型別以及沒找到還會try a setProperty("name", "value")等, setProperty可以處理比如AbstractEndpoint中有個HashMap attributes的屬性時會attributes.put(name, value)。 如果是ssl還會執行customizeSsl方法, 設置一些SSL用的屬性比如協定比如秘鑰還有可以用上秘鑰倉庫等。
prepareContext(tomcat.getHost, initializers), initializers這裡是AnnotationConfigEmbeddedWebApplicationContext, Context級的根;準備Context的過程主要設置Base目錄, new一個TomcatEmbeddedContext並在構造中判斷了下loadOnStartup方法是否被重寫;註冊一個FixContextListener監聽,
connector從socket接收的資料, 解析成HttpServletRequest後就會經過這幾層容器, 有容器各自的Value物件鏈依次處理。
接著是是否註冊jspServlet,jasperInitializer和StoreMergedWebXmlListener我這裡是都沒有的。 接著的mergeInitializers方法:
protected final ServletContextInitializer mergeInitializers( ServletContextInitializer... initializers) { List mergedInitializers = new ArrayList; mergedInitializers.addAll(Arrays.asList(initializers)); mergedInitializers.addAll(this.initializers); return mergedInitializers .toArray(new ServletContextInitializer[mergedInitializers.size()]); }configureContext(context, initializersToUse)對context做了些設置工作, 包括TomcatStarter(產生實體並set給context),LifecycleListener,contextValue,errorpage,Mime,session超時持久化等以及一些自訂工作:
TomcatStarter starter = new TomcatStarter(initializers); if (context instanceof TomcatEmbeddedContext) { // Should be true ((TomcatEmbeddedContext) context).setStarter(starter); } context.addServletContainerInitializer(starter, NO_CLASSES); for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) { context.addLifecycleListener(lifecycleListener); } for (Valve valve : this.contextValves) { context.getPipeline.addValve(valve); } for (ErrorPage errorPage : getErrorPages) { new TomcatErrorPage(errorPage).addToContext(context); } for (MimeMappings.Mapping mapping : getMimeMappings) { context.addMimeMapping(mapping.getExtension, mapping.getMimeType); }Session如果不需要持久化會註冊一個DisablePersistSessionListener。 其他定制化操作是通過TomcatContextCustomizer的實現類實現的:
context配置完了作為child add給host,add時給context註冊了個記憶體洩漏跟蹤的監聽MemoryLeakTrackingListener。 postProcessContext(context)方法是空的, 留給子類重寫用的。
getEmbeddedServletContainer方法的最後一行:return getTomcatEmbeddedServletContainer(tomcat)。
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer( Tomcat tomcat) { return new TomcatEmbeddedServletContainer(tomcat, getPort >= 0); }TomcatEmbeddedServletContainer的構造函數:
public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; initialize; }initialize的第一個方法addInstanceIdToEngineName對全域原子變數containerCounter+1, 由於初始值是-1, 所以addInstanceIdToEngineName方法內後續的獲取引擎並設置名字的邏輯沒有執行:
private void addInstanceIdToEngineName { int instanceId = containerCounter.incrementAndGet; if (instanceId > 0) { Engine engine = this.tomcat.getEngine; engine.setName(engine.getName + "-" + instanceId); } }initialize的第二個方法removeServiceConnectors, 將上面new的connection以service(這裡是StandardService[Tomcat])做key保存到private final Map serviceConnectors中,並將StandardService中的protected Connector connectors與service解綁(connector.setService((Service)null);),解綁後下面利用LifecycleBase啟動容器就不會啟動到Connector了。
之後是this.tomcat.start,這段比較複雜,我單獨總結一篇吧。
TomcatEmbeddedServletContainer的初始化,接下來是rethrowDeferredStartupExceptions,這個方法檢查初始化過程中的異常,如果有直接在主執行緒拋出,檢查方法是TomcatStarter中的private volatile Exception startUpException,這個值是在Context啟動過程中記錄的:
@Override public void onStartup(Set> classes, ServletContext servletContext) throws ServletException { try { for (ServletContextInitializer initializer : this.initializers) { initializer.onStartup(servletContext); } } catch (Exception ex) { this.startUpException = ex; // Prevent Tomcat from logging and re-throwing when we know we can // deal with it in the main thread, but log for information here. if (logger.isErrorEnabled) { logger.error("Error starting Tomcat context. Exception: " + ex.getClass.getName + ". Message: " + ex.getMessage); } } }Context context = findContext:
private Context findContext { for (Container child : this.tomcat.getHost.findChildren) { if (child instanceof Context) { return (Context) child; } } throw new IllegalStateException("The host does not contain a Context"); }綁定命名的上下文和classloader,不成功也無所謂:
try { ContextBindings.bindClassLoader(context, getNamingToken(context), getClass.getClassLoader); } catch (NamingException ex) { // Naming is not enabled. Continue }startDaemonAwaitThread方法的注釋是:與Jetty不同,Tomcat所有的執行緒都是守護執行緒,所以創建一個非守護執行緒來避免服務到這就shutdown了:
private void startDaemonAwaitThread { Thread awaitThread = new Thread("container-" + (containerCounter.get)) { @Override public void run { TomcatEmbeddedServletContainer.this.tomcat.getServer.await; } }; awaitThread.setContextClassLoader(getClass.getClassLoader); awaitThread.setDaemon(false); awaitThread.start; }回到EmbeddedWebApplicationContext,initPropertySources方法,用初始化好的servletContext完善環境變數:
/** * {@inheritDoc} *Replace {@code Servlet}-related property sources. */ @Override protected void initPropertySources { ConfigurableEnvironment env = getEnvironment; if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, null); } }
createEmbeddedServletContainer就結束了,內嵌容器的啟動過程至此結束。
==========================================================
咱最近用的github:https://github.com/saaavsaaa
將上面new的connection以service(這裡是StandardService[Tomcat])做key保存到private final Map serviceConnectors中,並將StandardService中的protected Connector connectors與service解綁(connector.setService((Service)null);),解綁後下面利用LifecycleBase啟動容器就不會啟動到Connector了。之後是this.tomcat.start,這段比較複雜,我單獨總結一篇吧。
TomcatEmbeddedServletContainer的初始化,接下來是rethrowDeferredStartupExceptions,這個方法檢查初始化過程中的異常,如果有直接在主執行緒拋出,檢查方法是TomcatStarter中的private volatile Exception startUpException,這個值是在Context啟動過程中記錄的:
@Override public void onStartup(Set> classes, ServletContext servletContext) throws ServletException { try { for (ServletContextInitializer initializer : this.initializers) { initializer.onStartup(servletContext); } } catch (Exception ex) { this.startUpException = ex; // Prevent Tomcat from logging and re-throwing when we know we can // deal with it in the main thread, but log for information here. if (logger.isErrorEnabled) { logger.error("Error starting Tomcat context. Exception: " + ex.getClass.getName + ". Message: " + ex.getMessage); } } }Context context = findContext:
private Context findContext { for (Container child : this.tomcat.getHost.findChildren) { if (child instanceof Context) { return (Context) child; } } throw new IllegalStateException("The host does not contain a Context"); }綁定命名的上下文和classloader,不成功也無所謂:
try { ContextBindings.bindClassLoader(context, getNamingToken(context), getClass.getClassLoader); } catch (NamingException ex) { // Naming is not enabled. Continue }startDaemonAwaitThread方法的注釋是:與Jetty不同,Tomcat所有的執行緒都是守護執行緒,所以創建一個非守護執行緒來避免服務到這就shutdown了:
private void startDaemonAwaitThread { Thread awaitThread = new Thread("container-" + (containerCounter.get)) { @Override public void run { TomcatEmbeddedServletContainer.this.tomcat.getServer.await; } }; awaitThread.setContextClassLoader(getClass.getClassLoader); awaitThread.setDaemon(false); awaitThread.start; }回到EmbeddedWebApplicationContext,initPropertySources方法,用初始化好的servletContext完善環境變數:
/** * {@inheritDoc} *Replace {@code Servlet}-related property sources. */ @Override protected void initPropertySources { ConfigurableEnvironment env = getEnvironment; if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, null); } }
createEmbeddedServletContainer就結束了,內嵌容器的啟動過程至此結束。
==========================================================
咱最近用的github:https://github.com/saaavsaaa