1、前言簡單介紹
SpringBoot的自動配置就是SpringBoot的精髓所在;對於SpringBoot項目是不需要配置Tomcat、jetty等等Servlet容器,直接啓動application類既可,SpringBoot爲什麼能做到這麼簡捷?原因就是使用了內嵌的Servlet容器,默認是使用Tomcat的,具體原因是什麼?爲什麼啓動application就可以啓動內嵌的Tomcat或者其它Servlet容器?ok,本文就已SpringBoot嵌入式Servlet的啓動原理簡單介紹一下
環境準備:
- SmartGit
- IntelliJ IDEA
- Maven
- SpringBoot2.2.1
本文先創建一個SpringBoot項目,基於最新的2.2.1版本,閱讀源碼之前先進行一些必要的應用代碼實踐,先學習應用實踐,有助於理解源碼
在SpringBoot官網找到嵌入式Servlet容器相關的描述:
Under the hood, Spring Boot uses a different type of ApplicationContext for embedded servlet container support. The ServletWebServerApplicationContext is a special type of WebApplicationContext that bootstraps itself by searching for a single ServletWebServerFactory bean. Usually a TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory has been auto-configured.
這是比較重要的信息,簡單翻譯一下,裏面提到ServletWebServerApplicationContext這是一種特殊的ApplicationContext 類,也就是啓動時候,用來掃描ServletWebServerFactory類型的類的,ServletWebServerFactory類是一個接口類,具體的實現類是TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory etc.
2、定製servlet容器
然後如何自定義嵌入式Servlet容器的配置?在官方文檔裏找到如圖對應的描述:
方法1:修改application配置
從官方文檔可以看出支持的配置有如下所示,所以要修改servlet容器配置,直接在application配置文件修改即可:
- 網絡設置: 監聽端口(server.port)、服務器地址(server.address)等等
- Session設置: 會話是否持久 (server.servlet.session.persistent),會話超時(server.servlet.session.timeout), 會話數據的位置 (server.servlet.session.store-dir), 會話對應的cookie配置 (server.servlet.session.cookie.*) 等等
- 錯誤管理: 錯誤頁面位置 (server.error.path)等等
- SSL設置:具體參考Configure SSL
- HTTP compression:具體參考Enable HTTP Response Compression
方法2:自定義WebServerFactoryCustomizer定製器類
從文檔裏還找到了通過新建自定義的WebServerFactoryCustomizer類來實現屬性配置修改,WebServerFactoryCustomizer也就是一種定製器類:
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;
/**
* <pre>
* 自定義的WebServerFactory定製器類
* </pre>
* @author nicky
* <pre>
* 修改記錄
* 修改後版本: 修改人: 修改日期: 2019年12月01日 修改內容:
* </pre>
*/
@Component
public class WebServerFactoryCustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(8081);
}
}
ok,官方文檔裏提供瞭如上的代碼實例,當然也可以通過@bean註解進行設置,代碼實例如:
@Configuration
public class MyServerConfig {
/**
* 自定義的WebServerFactory定製器類
* @return
*/
@Bean
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> webServerFactoryCustomizer(){
return new WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>() {
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
factory.setPort(8082);
}
};
}
}
3、變換servlet容器
SpringBoot2.2.1版本支持的內嵌servlet容器有tomcat、jetty(適用於長連接)、undertow(高併發性能不錯,但是默認不支持jsp),不過項目默認使用的是Tomcat的
我們可以找新建的一個SpringBoot項目,要求是集成了spring-boot-starter-web的工程,在pom文件右鍵->Diagrams->Show Dependencies,可以看到對應的jar關係圖:
ok,從圖可以看出,SpringBoot默認使用是Servlet容器是Tomcat,然後如果要切換其它嵌入式Servlet容器,要怎麼實現?我們可以在圖示選擇spring-boot-starter-tomcat右鍵exclusion,然後引入其它的servlet容器,pom配置如圖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
4、servlet容器啓動原理
ok,有了前面應用方面的學習之後,就可以簡單跟一下Springboot是怎麼對servlet容器進行自動配置的?內嵌的默認Tomcat容器是怎麼樣啓動的?
從之前博客的學習,可以知道Springboot的自動配置都是通過一些AutoConfiguration類進行自動配置的,所以同理本博客也找一些對應的類,ServletWebServerFactoryAutoConfiguration 就是嵌入式servlet容器的自動配置類,簡單跟一下其源碼
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)//使ServerProperties配置類起效
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })//@Import是Spring框架的註解,作用是將對應組件加載到容器,這裏關鍵的是BeanPostProcessorsRegistrar,一個後置處理類
public class ServletWebServerFactoryAutoConfiguration {
@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
return new ServletWebServerFactoryCustomizer(serverProperties);
}
//Tomcat的定製器類,起作用的條件是有Tomcat對應jar有引入項目的情況,默認是引入的,所以會執行Tomcat的servletWeb工廠定製類
@Bean
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}
....
//註冊重要的後置處理器類WebServerFactoryCustomizerBeanPostProcessor,在ioc容器啓動的時候會調用後置處理器
public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
//設置ConfigurableListableBeanFactory
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableListableBeanFactory) {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
WebServerFactoryCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
}
private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) {
if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
beanDefinition.setSynthetic(true);
registry.registerBeanDefinition(name, beanDefinition);
}
}
}
}
從自動配置類裏,我們並不能很明確地理解自動配置是怎麼運行的,只看重關鍵的一些信息點,比如註冊了Tomcat的ServletWebServer工廠的定製器類,方法是tomcatServletWebServerFactoryCustomizer,還有一個後置處理類BeanPostProcessorsRegistrar,後置處理是Spring源碼裏是很關鍵的,所以這裏可以繼續點一下TomcatServletWebServerFactoryCustomizer,Tomcat的webServer工廠定製器類
也是一個WebServerFactoryCustomizer類型的類,從前面的應用學習,這個類是進行servlet容器的一些定製
這個是關鍵的方法,主要是拿ServerProperties配置類裏的信息進行特定屬性定製
所以,這裏就可以知道Tomcat的配置是通過定製器類TomcatServletWebServerFactoryCustomizer進行定製的,其工廠類是TomcatServletWebServerFactory
TomcatServletWebServerFactory工廠類進行Tomcat對象的創建,必要參數的自動配置
ok,簡單跟了一下源碼之後,我們知道了TomcatServletWebServerFactoryCustomizer是Tomcat的定製器類,Tomcat對象的創建是通過TomcatServletWebServerFactory類的,然後,有個疑問,這個定製器類是什麼時候創建的?爲什麼一啓動Application類,嵌入式的Tomcat也啓動了?打成jar格式的Springboot項目,只要運行jar命令,不需要啓動任何servlet容器,項目也是正常運行的?然後這個BeanPostProcessorsRegistrar後置處理類有什麼作用?ok,帶着這些疑問,我們還是用調試一下源碼
如圖,打斷點調試,看看Tomcat定製器是怎麼創建的?
定製器類被調用了,其對應的工廠類也會起作用,打個斷點,看看工廠類是怎麼調用的?
ok,啓動Application類,在idea裏調試,如圖,可以跟着調用順序,一點點跟源碼,如圖所示,調用了Springboot的run方法
run方法裏的刷新上下文方法,refreshContext其實也就是創建ioc容器,初始化ioc容器,並創建容器的每一個組件
這裏注意到了,調用到了ServletWebServerApplicationContext類的refresh方法,ServletWebServerApplicationContext類前面也介紹到了,這個類是一種特殊的ApplicationContext類,也就是一些ioc的上下文類,作用於WebServer類型的類
創建webServer類,先創建ioc容器,調用基類的onRefresh方法,然後再調用createWebServer方法
ioc的servletContext組件沒被創建的情況,調用ServletWebServerFactory類獲取WebServer類,有servletContext的情況,直接從ioc容器獲取
掃描ioc容器裏是否有對應的ServletWebServerFactory類,TomcatServletWebServerFactory是其中一種,通過調試,是有掃描到的,所以從ioc容器裏將這個bean對應的信息封裝到ServletWebServerFactory對象
接着是ioc容器創建bean的過程,這個一個比較複雜的過程,因爲是單例的,所以是調用singleObjects進行存儲
bean被創建之後,調用了後置處理器,這個其實就是Spring的源碼裏的bean的創建過程,後置處理器是很關鍵的,在bean被創建,還沒進行屬性賦值時候,就調用了後置處理器
關鍵點,這裏是檢測是否有WebServerFactory工廠類,前面的調試發現已經有Tomcat的WebServer工廠類,所以是會調用後置處理器的
調用了WebServerFactoryCustomizerBeanPostProcessor這個後置處理類,然後拿到一個WebServerFactoryCustomizer定製器類,也就是TomcatWebServerFactoryCustomizer,通過後置處理器調用定製方法customize
然後WebServerFactoryCustomizerBeanPostProcessor這個後置處理器是什麼註冊的?往前翻Springboot的自動配置類,在這裏找到了WebServerFactoryCustomizerBeanPostProcessor的註冊
ok,繼續調試源碼,BeanWrapperImpl創建bean實例
ok,Tomcat定製器類被調用了,是通過後置處理器調用的
然後就是之前跟過的定製方法customize執行:
Tomcat的WebServer工廠類創建Tomcat對象實例,進行屬性配置,引擎設置等等
端口有設置就創建TomcatwebServer對象
TomcatWebServer啓動Tomcat,如圖代碼所示:
ok,跟了源碼,您是否有一個疑問?Tomcat的工廠類TomcatServletWebServerFactory是什麼時候創建的?好的,返回前面配置類看看,如圖,這裏用import引入了EmbeddedTomcat類
ok,EmbeddedTomcat是一個內部的配置類,條件是有引入Tomcat對應的jar,就會自動創建工廠類,很顯然,Springboot默認是有引入的
ok,經過源碼比較簡單的學習,思路就很清晰了
Springboot的ServletWebServerFactoryAutoConfiguration是嵌入式Servlet容器的自動配置類,這個類的主要作用是創建TomcatServletWebServerFactory工廠類,創建定製器類TomcatServletWebServerFactoryCustomizer,創建FilterRegistrationBean類,同時很關鍵的一步是註冊後置處理器webServerFactoryCustomizerBeanPostProcessor
然後Springboot的Application類一啓動,就會執行run方法,run經過一系列調用會通過ServletWebServerApplicationContext的onRefresh方法創建ioc容器,然後通過createWebServer方法,createWebServer方法會去ioc容器裏掃描是否有對應的ServletWebServerFactory工廠類(TomcatServletWebServerFactory是其中一種),掃描得到,就會觸發webServerFactoryCustomizerBeanPostProcessor後置處理器類,這個處理器類會獲取TomcatServletWebServerFactoryCustomizer定製器,並調用customize方法進行定製,這時候工廠類起作用,調用getWebServer方法進行Tomcat屬性配置和引擎設置等等,再創建TomcatWebServer啓動Tomcat容器
ok,本問只是簡單跟一下嵌入式Tomcat容器的啓動過程,可以看出Springboot的強大,還是基於Spring框架的,比如本博客提到的後置處理器,以及bean工程創建bean實例的過程,都是通過Spring框架實現的