1. 程式人生 > >SpringBoot原始碼學習系列之嵌入式Servlet容器

SpringBoot原始碼學習系列之嵌入式Servlet容器

目錄

  • 1、部落格前言簡單介紹
  • 2、定製servlet容器
  • 3、變換servlet容器
  • 4、servlet容器啟動原理

SpringBoot原始碼學習系列之嵌入式Servlet容器啟動原理

@

1、部落格前言簡單介紹

SpringBoot的自動配置就是SpringBoot的精髓所在,對於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框架實