1. 程式人生 > >Spring Boot 嵌入式Web容器

Spring Boot 嵌入式Web容器

目錄

  • 前言
  • 1、起源
  • 2、容器啟動流程解析
    • 2.1、獲取應用型別
    • 2.2、容器啟動流程
  • 3、載入 Web 容器工廠
  • 4、總結

前言

        最近在學習Spring Boot相關的課程,過程中以筆記的形式記錄下來,方便以後回憶,同時也在這裡和大家探討探討,文章中有漏的或者有補充的、錯誤的都希望大家能夠及時提出來,本人在此先謝謝了!

開始之前呢,希望大家帶著幾個問題去學習:
1、Spring Boot 嵌入式Web容器是什麼?
2、整體流程或結構是怎樣的?
3、核心部分是什麼?
4、怎麼實現的?
這是對自我的提問,我認為帶著問題去學習,是一種更好的學習方式,有利於加深理解。好了,接下來進入主題。

1、起源

        在當今的網際網路場景中,與終端使用者互動的應用大多數是 Web 應用,其中 Java Web 應用尤為突出,其對應的 Java Web 容器發展至今也分為 Servlet Web 容器和 Reactive Web 容器,前者的使用率大概佔比是百分之九十左右,其具體的實現有 Tomcat

JettyUndertow;而後者出現較晚,且技術棧體系並未完全成熟,還有待時間驗證可行性,它的預設實現為 Netty Web Server。其中的 Servlet 規範與三種 Servlet 容器的版本關係如下:

Servlet 規範 Tomcat Jetty Undertow
4.0 9.X 9.X 2.X
3.1 8.X 8.X 1.X
3.0 7.X 8.X N/A
2.5 6.X 8.X N/A

以上 Web 容器均被 Spring Boot 嵌入至其中作為其核心特性,來簡化 Spring Boot應用啟動流程。Spring Boot 通過 Maven

依賴來切換應用的嵌入式容器型別,其對應的 Maven jar 分別是:

<!-- Tomcat -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>

<!-- undertow -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

<!-- jetty -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

<!-- netty Web Server -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-reactor-netty</artifactId>
</dependency>

前三者是 Servlet Web 實現,最後則是 Reactive Web 的實現。值得注意的是,當我們引用的是 Servlet Web 功能模組時,它會自動整合 Tomcat ,裡面包含了 TomcatMaven 依賴包,也就是說 Tomcat 是預設的 Servlet Web 容器。Servlet Web 模組的 Maven 依賴如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

而如果引用的是 Reactive Web 功能模組時,則會預設整合 netty Web ServerReactive Web 模組的依賴如下:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

不過,上面的三種 Servlet Web 容器也能作為 Reactive Web 容器 ,並允許替換預設實現 Netty Web Server,因為 Servlet 3.1+容器同樣滿足 Reactive 非同步非阻塞特性。

接下來,我們重點討論嵌入式 Web 容器的啟動流程。

注:本篇文章所用到的 Spring Boot版本是 2.1.6.BUILD-SNAPSHOT

2、容器啟動流程解析

        Spring Boot 嵌入式容器啟動時會先判斷當前的應用型別,是 Servlet Web 還是 Reactive Web ,之後會建立相應型別的 ApplicationContext 上下文,在該上下文中先獲取容器的工廠類,然後利用該工廠類建立具體的容器。接下來,我們進行詳細討論。

Spring Boot 啟動類開始:

@SpringBootApplication
public class DiveInSpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(DiveInSpringBootApplication.class, args);
    }
}

2.1、獲取應用型別

先來看看,獲取應用型別的過程,進入 run 的過載方法:

public class SpringApplication {

    // 1、該方法中,先構造 SpringApplication 物件,再呼叫該物件的 run 方法。我們進入第二步檢視構造過程
    public static ConfigurableApplicationContext run(Class<?>[] primarySources,
            String[] args) {
        return new SpringApplication(primarySources).run(args);
    }
    
    // 2、webApplicationType 儲存的就是應用的型別,通過 deduceFromClasspath 方法返回。
    // 我們進入第三步檢視該方法實現
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        ...
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        ...
    }
}

public enum WebApplicationType {

    private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };

    private static final String WEBMVC_INDICATOR_CLASS = "org.springframework." + "web.servlet.DispatcherServlet";

    private static final String WEBFLUX_INDICATOR_CLASS = "org." + "springframework.web.reactive.DispatcherHandler";

    private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

    // 3、這裡其實是根據引入的 Web 模組 jar 包,來判斷是否包含各 Web 模組的類,來返回相應的應用型別
    static WebApplicationType deduceFromClasspath() {
    
        // 當 DispatcherHandler 類存在,DispatcherServlet 和 ServletContainer 不存在時,
        // 返回 Reactive ,表示當前 Spring Boot 應用型別是 Reactive Web 。
        // 前者是 Reactice Web jar 中的類,後兩者是 Servlet Web 中的。
        if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
                && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        
        // 這裡判斷是非 Web 應用型別
        for (String className : SERVLET_INDICATOR_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        
        // 以上都不滿足時,最後返回 Servlet 。
        return WebApplicationType.SERVLET;
    }
}

以上,在 SpringApplication 的構造階段確定了當前應用的型別,該型別名稱儲存在 webApplicationType 欄位中。

2.2、容器啟動流程

接著進入容器啟動流程,進入過載的 run 方法中:

public class SpringApplication {

    public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
            + "annotation.AnnotationConfigApplicationContext";

    public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
            + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
            
    public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
            + "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
            
    ...
    
    private WebApplicationType webApplicationType;
    
    ...
    
    public ConfigurableApplicationContext run(String... args) {
        ...
        
        ConfigurableApplicationContext context = null;
        
        try {
            ...
            // 1、通過 createApplicationContext 方法建立對應的 ApplicationContext 應用上下文,進入 1.1 檢視具體實現
            context = createApplicationContext();
            
            ...
            
            // 2、該方法實質是啟動 Spring 應用上下文的,但 Spring Boot 嵌入式容器也在該過程中被啟動,入參是上下文物件,我們進入 2.1 進行跟蹤
            refreshContext(context);
            
            ...
        }
        ...
    }
    
    // 1.1、
    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
            
                // 這裡就是通過 webApplicationType 屬性,判斷應用型別,來建立不同的 ApplicationContext 應用上下文
                switch (this.webApplicationType) {
                case SERVLET:
                    
                    // 返回的是 Servlet Web ,具體物件為 AnnotationConfigServletWebServerApplicationContext,
                    // 該類有一個關鍵父類 ServletWebServerApplicationContext
                    contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                
                    // 返回的是 Reactive Web,具體物件為 AnnotationConfigReactiveWebServerApplicationContext
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                
                    // 應用型別是非 Web 時,返回 AnnotationConfigApplicationContext
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                }
            }
            ...
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
    
    // 2.1
    private void refreshContext(ConfigurableApplicationContext context) {
        
        // 裡面呼叫的是 refresh 方法,進入 2.2 繼續跟蹤
        refresh(context);
        
        ...
    }
    
    // 2.2
    protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        
        // 最終呼叫了 所有應用上下文的統一抽象類 AbstractApplicationContext 中的 refresh 方法,進入 3 檢視實現
        ((AbstractApplicationContext) applicationContext).refresh();
    }
    
    ...
}

AbstractApplicationContextSpring 應用上下文的核心啟動類,Spring 的 ioc 從這裡就開始進入建立流程。在後續在 Spring 系列的文章中會進行詳細討論,我們這裡只關注容器建立的部分。

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
    
    ...
    
    // 3
    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            
            try {
                ...
                
                // Web 容器在這個方法中啟動,但在當前抽象類中這個方法是個空實現,具體由 ApplicationContext 上下文的子類物件進行重寫,
                // 我們進入 4 檢視其中一個子類的實現
                onRefresh();

                ...
            }
        }
        ...
    }
    ...
}

這裡以 Servlet web 為例,具體上下文物件是 AnnotationConfigServletWebServerApplicationContext,該類實現了 ServletWebServerApplicationContext 類,onRefresh 方法由該類進行重寫。

public class ServletWebServerApplicationContext extends GenericWebApplicationContext
        implements ConfigurableWebServerApplicationContext {
    ...
    
    // 4
    @Override
    protected void onRefresh() {
        
        ...
        
        try {
        
            // 裡面呼叫了 createWebServer 方法,進入 5 檢視實現
            createWebServer();
        }
        catch (Throwable ex) {
            throw new ApplicationContextException("Unable to start web server", ex);
        }
    }
    
    ...
    
    // 5
    private void createWebServer() {
        
        // WebServer 就是容器物件,是一個介面,其對應的實現類分別是:
        // JettyWebServer、TomcatWebServer、UndertowWebServer、NettyWebServer。這些容器物件由對應的容器工廠類進行建立
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        
        // 當 webServer 等於 null ,也就是容器還沒建立時,進入該 if 中
        if (webServer == null && servletContext == null) {
            
            // 這裡是獲取 建立 Servlet Web 容器的工廠類,也是一個介面,有三個實現,分別是:
            // JettyServletWebServerFactory、TomcatServletWebServerFactory、UndertowServletWebServerFactory
            ServletWebServerFactory factory = getWebServerFactory();
            
            // 通過容器工廠類的 getWebServer 方法,建立容器物件。這裡以建立 Tomcat 為例,來繼續跟蹤容器的建立流程,
            // 跳到 6 檢視 TomcatServletWebServerFactory 的 getWebServer 方法
            this.webServer = factory.getWebServer(getSelfInitializer());
        }
        
        ...
    }
}

TomcatServletWebServerFactory 是建立 Tomcat 的 Web 容器工廠類,但這個工廠類是如何被建立的呢?這裡將會在文章的第三部分進行詳細討論。

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
        implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {
    
    ...
    // 6、
    @Override
    public WebServer getWebServer(ServletContextInitializer... initializers) {
        
        // 方法中先進行建立 Tomcat 的流程,如 Container 、Engine、Host、Servlet 幾個容器的組裝。
        // 後續有機會再對 Tomcat 進行詳細討論,這裡就不深入了
        Tomcat tomcat = new 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());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers);
        
        // 通過 getTomcatWebServer 方法返回 TomcatWebServer 容器物件。進入 7 檢視接下來的流程
        return getTomcatWebServer(tomcat);
    }
    
    ...
    
    // 7、這裡通過 TomcatWebServer 的構造方法建立該物件。進入 8 繼續跟蹤
    protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
        return new TomcatWebServer(tomcat, getPort() >= 0);
    }
    
    ...
}

TomcatWebServer 是具體的容器物件,在其對應的工廠類中進行建立,其實現了 WebServer 介面,並在該物件中進行 Tomcat 的啟動流程。

public class TomcatWebServer implements WebServer {

    ...

    // 8、
    public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        
        // 進入 initialize 方法中,檢視實現
        initialize();
    }

    private void initialize() throws WebServerException {
        logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
        synchronized (this.monitor) {
            try {
            
                ...

                // 呼叫 Tomcat 的 start 方法,正式啟動
                this.tomcat.start();

                ...
                
                // 啟動守護執行緒來監聽http請求 
                startDaemonAwaitThread();
            }
            ...
        }
    }
    
    ...
}

到這裡,Web 容器的啟動流程就結束了,以上是以 Servlet Web 及其具體的 Tomcat 容器為例子進行的討論,這也是大部分開發者常用的體系。接著來對整個過程做一個總結。

  • 首先通過引入的 Web 模組 Maven 依賴 ,來判斷當前應用的型別,如 Servlet Web 或 Reactive Web。並根據應用型別來建立相應的 ApplicationContext 上下文物件。
  • 然後呼叫了 ApplicationContext 的父抽象類 AbstractApplicationContext 中的 refresh 方法,又在該方法中呼叫了子類的 onRefresh 方法。
  • 最後是在 onRefresh 中通過容器的工廠類建立具體容器物件,並在該容器物件中進行啟動。

3、載入 Web 容器工廠

        上面說過, Web 容器物件是由其對應的工廠類進行建立的,那容器工廠類又是怎麼建立呢?我們這裡就來看一看。在《Spring Boot 自動裝配(二)》的 1.2 小節說過, Spring Boot 啟動時,會讀取所有 jar 包中 META-INF 資料夾下的 spring.factories 檔案,並載入檔案中定義好的類,如:

...
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\

...

其中有一個 ServletWebServerFactoryAutoConfiguration 類:

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
        ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
        ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
    ...
}

該類通過 @Import 匯入了 ServletWebServerFactoryConfiguration 中的三個內部類,這三個內部類就是用來建立容器工廠,我們進入其中檢視具體實現:

@Configuration
class ServletWebServerFactoryConfiguration {

    // 通過 @ConditionalOnClass 判斷 Servlet 、Tomcat、UpgradeProtocol 這三個 Class 是否存在,
    // 當引用的是 Tomcat Maven 依賴時,則 Class 才存在,並建立 Tomcat 的容器工廠類 TomcatServletWebServerFactory
    @Configuration
    @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedTomcat {

        @Bean
        public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
            return new TomcatServletWebServerFactory();
        }
    }

    // 當引用的是 Jetty Maven 依賴時,@ConditionalOnClass 條件才滿足,
    // 建立的容器工廠類是 JettyServletWebServerFactory 
    @Configuration
    @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedJetty {

        @Bean
        public JettyServletWebServerFactory JettyServletWebServerFactory() {
            return new JettyServletWebServerFactory();
        }
    }

    // 當引用的是 Undertow Maven 依賴時,@ConditionalOnClass 條件才滿足,
    // 建立的容器工廠類是 UndertowServletWebServerFactory 
    @Configuration
    @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedUndertow {

        @Bean
        public UndertowServletWebServerFactory undertowServletWebServerFactory() {
            return new UndertowServletWebServerFactory();
        }
    }

可以看到,主要是通過引入相應 Web 容器的 Maven 依賴,來判斷容器對應的 Class 是否存在,存在則建立相應的容器工廠類。

4、總結

        最後,來對 Spring Boot 嵌入式 Web 容器做一個整體的總結。Spring Boot 支援的兩大 Web 容器體系,一個是 Servlet Web ,另一個是 Reactive Web ,它們都有其具體的容器實現,相信大多數開發者使用的都是前者,且最常用的容器實現也是 Tomcat,所以這篇文章主要討論的也是 Spring Boot 啟動 Tomcat 嵌入式容器的流程。

以上就是本章的內容,如果文章中有錯誤或者需要補充的請及時提出,本人感激不