SpringBoot內嵌Tomcat的啓動過程理解
版本:SpringBoot:2.1.1.RELEASE Spring Framework:5.1.3 注意:不同版本實現有差異;
概述:Tomcat是如何在SpringBoot中啓動的,這一篇文件會進行簡單分析,關注主流程。
注意:
1.本文可能存在錯誤,建議IDE斷點調試,加以理解。配合各種圖形理解源碼,會更好。
2.閱讀源碼關注註釋行即可,不必追究每行代碼! 篇幅偏長,建議自行拆解。
3.建議對自動裝配、refresh() 啓動過程、getBean的創建過程有一定的理解。
4.本文僅做參考,如果想理解掌握,最好自己動手實踐。
5.Servlet 容器,就以Tomcat爲例子。
一、主導權的變化
1.1 SpringMVC + Spring
SpringMVC + Spring的時代,項目打包成war、部署到Tomcat這樣的Servlet容器中,由Servl容器啓動,伴隨着Listener和Servlet的生命週期回調、來創建Spring父子容器。不得不說Tomcat這樣的Servlet容器是具有主導地位的,即Servlet容器來驅動創建Spring容器。江湖地位不言而喻。
1.1 Tomcat 啓動,加載讀取Web.xml。容器裝載ContextLoaderListener和DispatcherServlet。
1.2 在此Tomcat具有主導地位。
1.2 SpringBoot
當SpringBoot來臨的時候,江湖地位發生顛覆性變化。Tomcat、以及相關組件等被封裝成爲Spring Bean。由Spring容器的生命週期來驅動Tomcat的創建、啓動。
1.1 Tomcat,由自身提供嵌入式相關API。SpringBoot 在Tomcat自身提供的嵌入式API的基礎上,進行了整合創新。注:Tomcat的嵌入核心能力,並非由SpringBoot提供。
1.2 Spring容器的生命週期來驅動Tomcat的創建啓動。
1.3 小結
上訴只是陳訴了目前的現狀,那麼SringBoot是如何讓Spring掌握主動權的,源碼見真知。
二、源碼見真知
閱讀源碼過程建議配合斷點調試,所謂紙上得來終覺淺,絕知此事要躬行!
關於tomcat的的啓動和創建大致如下(忽略分支判斷等情況,關注主流程)
注意:factory.getWebServer(getSelfInitializer());
是核心方法;裏面涵蓋了大量的邏輯。包括Tomcat的創建、初始化等一系列操作。(單獨會講解)
WebServer對象是對Tomcat、Jetty等的門面包裝。
2.1 啓動
模板模式,是Spring中非常常用的設計模式。AbstractApplicationContext中refresh() 提供了模板方法,子類覆蓋,從而實現擴展。
在AbstractApplicationContext的refresh()中,存在一個onRefresh,這個方法留個了子類擴展。所以Tomcat就在這裏創建,注:這個時候所有的BeanDefinition已經被加載的容器中。僅僅是部分BeanDefinition還沒進行初始化(注意:是BeanDefinition哦);在這個地方創建Tomcat是不錯的選擇。
//AbstractApplicationContext
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
……
// Initialize other special beans in specific context subclasses.
onRefresh(); //子類擴展。在ServletWebServerApplicationContext#createWebServer
……
}
通過斷點得到了目標:ServletWebServerApplicationContext
//ServletWebServerApplicationContext
@Override
protected void onRefresh() {
super.onRefresh(); //先調用父類的onRefresh,再額外創建內嵌的Servlet容器
try {
createWebServer(); //創建內嵌ServletWebServer
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
執行這個方法時,當前應用上下文中的所有BeanDefinition都已經加載,且對Bean進行初始化的相關組件如:BeanFactoryPostProcessors和BeanPostProcessor也都已經初始化完成。也即整個Bean創建的生命週期中所需要的組件都準備就緒,此時無論需要使用任何Bean。都可以完整可靠地創建出來。
BeanFactory可以將BeanDefinition轉換成你想要的Bean。可以通過name、Type等多種方式獲取。這也是BeanFactory容器強大的好處。
2.2 createWebServer
ServletWebServerApplicationContext 中創建WebServer。在這一步驟完成了tomcat的創建和初始化工作。
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) { //一開始爲空。後續完成
//得到Servelt工廠
ServletWebServerFactory factory = getWebServerFactory();
//傳入參數ServletContextInitializer
//在ServletContainer初始化完成以後,會調用其onStartup方並傳入
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
//如果webServer和servletContext 都存在的情況
//那什麼情況會出現呢?
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
//初始化PropertySource,用於使用Servlet
initPropertySources();
}
getSelfInitializer 只有等Servlet容器初始化完成的時候執行。這個方法會註冊一些Servlet上下文特有的組件和作用域。同時會獲取 ApplicationContext中所有
ServletContextInitializer 類型的Bean。統一調用他們的onStartup方法。
預告:getSelfInitializer() 創建一個org.springframework.boot.web.servlet.ServletContextInitializer匿名類,而onStartup指向 selfInitialize(ServletContext servletContext)
在 selfInitialize(ServletContext servletContext) 中實現了Servlet註冊到ServletContext 的功能。
上圖通過執行表達式,得到了多個RegistrationBean。其中有一個就是DispatcherServelet的包裝對象。
2.3 getWebServerFactory
創建ServletWebServerFactory 涉及到過程比較複雜,包括自動裝配、ServletWebServerFactory的一些定製初始化過程。如下圖所示:
-
創建ServletWebServerFactory對象
-
此過程會經歷後處理器的中方法的處理。例如WebServerFactoryCustomizerBeanPostProcessor,在這個過程中,會遍歷註冊的WebServerFactoryCustomizer集合
-
在這個過程中,會對ServletWebServerFactory做一些配置信息的處理。這些配置信息來源用戶自定義,例如
server.port
端口信息等
藉助BeanFactory 獲取ServletWebServerFactory類型的ServletWeb工廠
protected ServletWebServerFactory getWebServerFactory() {
//從BeanFactory中搜索ServletWebServerFactory類型
String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
+ "ServletWebServerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class); //過程參考上圖,Bean的創建過程遠遠比上圖複雜。
}
默認web環境就是TomcatServletWebServerFactory。不得不說,創建ServletWebServerFactory對象和對ServletWebServerFactory對象做屬性設置是分開的兩個過程。WebServerFactoryCustomizerBeanPostProcessor使這兩個過程不耦合,從而增加擴展能力!不過這麼玩還是蠻複雜的喲!
2.4 ServletWebServerFactory 來源
ServletWebServerFactory的來源和創建是什麼時候呢; 在默認情況下,ServletWebServerFactory接口的實現類有三個,其中一些公關方法被提取到抽象父類中。
接下來以TomcatServletWebServerFactory爲例,探索Servlet容器工廠Bean的註冊過程
,在SpringBoot中採用了大量的自動裝配,而ServletWebServerFactory的實現也利用了自動裝配的特性。當然,這個類還不是以AutoConfiguration結尾的類,所以可能還有其他類做應道。一般是XXXAutoConfiguration。(自動裝配能力,利用SPI、@Import、Conditional等實現條件自動裝配)
@Configuration
class ServletWebServerFactoryConfiguration {
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class }) //同時具有Servlet和Tomcat兩個類的時候
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) //不存在此ServletWebServerFactory類型的class
public static class EmbeddedTomcat {
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
}
…… //剩餘的容器工廠創建
}
ServletWebServerFactoryConfiguration類的 引入,則依託SpringBoot的autoconfigure包,這個包下面的ServletWebServerFactoryAutoConfiguration提供了對ServletWebServerFactoryConfiguration引入。這是自動裝配的規則屬性。
注:關注註釋行即可
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) //優先級最高
@ConditionalOnClass(ServletRequest.class) // ServletRequest類存在
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class) //開啓ServerProperties屬性,用於配置server相關的。例如端口號
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, //導入BeanPostProcessors類
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
//用於ServerProperties 對ServletWebServerFactory 定製化
@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
return new ServletWebServerFactoryCustomizer(serverProperties);
}
//Tomcat 存在;使用ServerProperties 定製。例如端口信息等
@Bean
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}
/**註冊WebServerFactoryCustomizerBeanPostProcessor。
* Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via
* {@link ImportBeanDefinitionRegistrar} for early registration.
*/
public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
……
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
//註冊WebServerFactoryCustomizerBeanPostProcessor,用於加載WebServerFactoryCustomizer
//對 WebServerFactoryCustomizer 進行個性化設置
registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
WebServerFactoryCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
}
……
}
}
//這個方法在Bean的初始化過程中被調用。用於對ServeletWebServerFactory進行自定義
@FunctionalInterface
public interface WebServerFactoryCustomizer<T extends WebServerFactory> {
void customize(T factory);
}
2.5 factory.getWebServer
得到一個Tomcat的容器。實現了創建並啓動.
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat(); //創建Tomcat實例
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false); //不自動部署
configureEngine(tomcat.getEngine()); // 配置Tomcat的引擎。
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
//準備Tomcat的StandardContext,並添加到Tomcat中,同時把initializers 註冊到類型爲
//TomcatStarter的ServletContainerInitializer中
prepareContext(tomcat.getHost(), initializers);
//將創建好的Tomcat。包裝成WebServer返回,設計對initializers#onStartup的回調。啓動了Tomcat的start方法,做初始化操作
return getTomcatWebServer(tomcat);
}
當然Tomcat還沒有真正打印端口,算不上結束
2.6 啓動
打印啓動Tomcat啓動日誌,在這個地方最終實現tomcat的啓動流程。
#ServletWebServerApplicationContext#finishRefresh
@Override
protected void finishRefresh() {
super.finishRefresh();
WebServer webServer = startWebServer(); //啓動容器。
if (webServer != null) {
publishEvent(new ServletWebServerInitializedEvent(webServer, this)); //發佈事件ServletWebServerInitializedEvent
}
}
private WebServer startWebServer() {
WebServer webServer = this.webServer;
if (webServer != null) {
webServer.start(); //開始啓動容器了
}
return webServer;
}
2.7 小結
下面總結一下ServerletWebServer的註冊、創建與啓動的過程。
當然,getSelfInitializer()調用細節涉及DispatcherServlet的裝載,在此進行簡單說明!
三、DispatcherServlet裝載
DispatcherServlet的創建和註冊;通過getSelfInitializer() 方法去請求相關的ServletContextInitializer組件,讓後調用他們的onStartup方法,實現往ServletContext註冊組件功能
分析一下:ServletContextInitializer的組件的起源吧
3.1 自動裝配
關於DispatcherServlet 這個Bean的裝配與ServletWebServerFactory的方式如出一轍,都是使用自動裝配的結果。
關注註釋行代碼
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class) //條件DispatcherServlet 存在
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class) //晚於ServletWebServerFactoryAutoConfiguration
public class DispatcherServletAutoConfiguration {
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Configuration
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class }) //WebMvcProperties 配置DispatcherServlet
protected static class DispatcherServletConfiguration { //關於DispatcherServlet的配置類
……
//名稱爲dispatcherServlet的類
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet(); //創建並配置一些必要信息
dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());
dispatcherServlet
.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setEnableLoggingRequestDetails(this.httpProperties.isLogRequestDetails());
return dispatcherServlet;
}
……
}
//下面這個類讓DispatcherServlet註冊到Tomcat容器的關鍵類。
@Configuration
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
……
//dispatcherServletRegistration
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet) {
//傳入DispatcherServlet。 構造一個DispatcherServletRegistrationBean。
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
this.webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); //名稱
registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup()); //配置啓動是否記載
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
}
……
}
}
DispatcherServletRegistrationBean 這個類包裝了DispatcherServlet。 而DispatcherServletRegistrationBean 作爲ServletContextInitializer
子類,會在Tomcat啓動的時候進行回調。
3.2 RegistrationBean
基於Servlet3.0的註冊的Bean的基類。RegistrationBean的實現類有Servlet、Filter、Listener ;三種組件。他們的實現方式都差不多一個樣
想ServletContext註冊組件的操作就在RegistrationBean以及子類中。
在DispatcherServletRegistrationBean 父類ServletRegistrationBean#addRegistration 實現了Servlet的添加工作。
public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<ServletRegistration.Dynamic> {
……
//添加Servlet到ServletContext的操作
@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
String name = getServletName();
return servletContext.addServlet(name, this.servlet); //註冊組件
}
}
3.3 ServletContextInitializer 調用鏈條
關於ServletContextInitializer的調用如下:
在這個類繼承中,使用了模板模式,在父類中定義了抽象方法,在子類中實現某個具體的註冊組件。在Servlet中,最終會調用ServletRegistrationBean的addRegistration方法實現組件的註冊。
3.4 整體流程如下
- 通過自動裝配,引入DispatcherServlet。然後被封裝成ServletRegistrationBean,註冊到ServletWebServer中
- 在Tomcat創建階段,把 getSelfInitializer() 傳入到TomcatStarter中
- 在TomcatStarter啓動是會調用 getSelfInitializer()的onStartup() 方法
- 在getSelfInitializer() 中會獲取所有 ServletContextInitializer的類型的Bean、調用他們的onStartup方法。
- RegistrationBean的onStartup方法就在此進行調用。實現Servlet、Filter、Listener註冊到ServletContext的過程。
下圖是其大致的調用鏈條:
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
//會在TomcatStarter進行調用。將RegistrationBean的onStartup進行調用
//從而註冊各個組件到ServletContext中
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext); // 註解註冊過程
}
}
四、總結
1.本篇主要談了Tomcat容器在SpringBoot的創建流程。其中涉及的很多分支並未進行詳細探索,感興趣的可以自行研究。
2.關於自動裝配的功能、Tomcat的啓動細節等未做深入理解。
3.閱讀源碼建議配合各種圖形來解決,從整體上去理解,避免陷入舍本逐末。
4.文章中可能會存在一些bug,建議結合源碼斷點調試進行理解。