springboot原始碼解析-管中窺豹系列之web伺服器(七)

豐極發表於2021-02-04

一、前言

  • Springboot原始碼解析是一件大工程,逐行逐句的去研究程式碼,會很枯燥,也不容易堅持下去。
  • 我們不追求大而全,而是試著每次去研究一個小知識點,最終聚沙成塔,這就是我們的springboot原始碼管中窺豹系列。

 簡介

二、web伺服器

  • 以前的的spring專案或者springmvc專案都需要一個web伺服器,tomcat,或者其它的
  • 使用springboot之後,我們不再需要配置web伺服器,因為springboot幫我們整合了
  • 今天我們來分析一下原始碼,看看在哪裡實現的,知其然知其所以然

三、原始碼分析

  • 還是從SpringApplication的run方法開始看
  • 不熟悉的可以看之前的文章:springboot原始碼解析-管中窺豹系列之總體結構(一)
SpringApplication.java
public ConfigurableApplicationContext run(String... args) {
 
    ...

    try {
        
        ...

        refreshContext(context);
        
        ...

    }
    catch (Throwable ex) {
        
        ...

    }

    ...

    return context;
}
  • 接著進入到 refreshContext(context) 裡面
AbstractApplicationContext.java

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {

        ...

        try {

            ...

            // Initialize other special beans in specific context subclasses.
            onRefresh();

            ...
        }

        catch (BeansException ex) {
            
            ...

        }

        finally {
            
            ...

        }
    }
}
  • 進入到 onRefresh() 方法
protected void onRefresh() throws BeansException {
    // For subclasses: do nothing by default.
}
  • 注意這個是一個protected方法,我們進入到子實現裡面
  • 具體用的哪個context,請看之前的文章:springboot原始碼解析-管中窺豹系列之專案型別(二)
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
        + "annotation.AnnotationConfigApplicationContext";

public static final String DEFAULT_SERVLET_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";

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                break;
            case REACTIVE:
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
            default:
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
  • 最常用的就是普通web專案,我們看這一個
  • 我們到org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext裡面找 onRefresh() 方法
  • 沒找到,在父類ServletWebServerApplicationContext裡面找到了
ServletWebServerApplicationContext.java

@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}
  • 我們到 createWebServer() 方法裡面
private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
        ServletWebServerFactory factory = getWebServerFactory();
        this.webServer = factory.getWebServer(getSelfInitializer());
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context",
                    ex);
        }
    }
    initPropertySources();
}
  • 用的工廠模式,先找到工廠getWebServerFactory()
  • 再用工廠生成webServer, factory.getWebServer(getSelfInitializer())
  • 先看看 getWebServerFactory() 這個方法
protected ServletWebServerFactory getWebServerFactory() {
    // Use bean names so that we don't consider the hierarchy
    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);
}
  • 獲取唯一的工廠:ServletWebServerFactory,多了少了都不行
  • 在哪載入進springboot的呢?
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    <scope>compile</scope>
</dependency>
  • spring-boot-starter-web裡面是包含了spring-boot-starter依賴的
  • spring-boot-starter裡面包含了spring-boot-autoconfigure依賴
  • spring-boot-autoconfigure裡面有一個類:ServletWebServerFactoryConfiguration
  • 這個類裡面有一個靜態類: EmbeddedTomcat
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {

    @Bean
    TomcatServletWebServerFactory tomcatServletWebServerFactory(
            ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
            ObjectProvider<TomcatContextCustomizer> contextCustomizers,
            ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        factory.getTomcatConnectorCustomizers()
                .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
        factory.getTomcatContextCustomizers()
                .addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
        factory.getTomcatProtocolHandlerCustomizers()
                .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
        return factory;
    }

}
  • ConditionOnClass : 它依賴 Servlet.class, Tomcat.class, UpgradeProtocol.class
  • ConditionalOnMissingBean: 防止重複載入
  • 至此,factory怎麼載入進spring就找到了
  • 我們多看一點,這個類ServletWebServerFactoryConfiguration還有兩個靜態類
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedJetty {

    @Bean
    JettyServletWebServerFactory JettyServletWebServerFactory(
            ObjectProvider<JettyServerCustomizer> serverCustomizers) {
        JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
        factory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList()));
        return factory;
    }

}


@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedUndertow {

    @Bean
    UndertowServletWebServerFactory undertowServletWebServerFactory(
            ObjectProvider<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers,
            ObjectProvider<UndertowBuilderCustomizer> builderCustomizers) {
        UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
        factory.getDeploymentInfoCustomizers()
                .addAll(deploymentInfoCustomizers.orderedStream().collect(Collectors.toList()));
        factory.getBuilderCustomizers().addAll(builderCustomizers.orderedStream().collect(Collectors.toList()));
        return factory;
    }

}
  • 一個是生成jetty伺服器工廠,一個是生成undertow伺服器工廠
  • 它們的載入,取決於依賴的class是否存在
  • tomcat: Servlet.class, Tomcat.class, UpgradeProtocol.class
  • jetty: Servlet.class, Server.class, Loader.class, WebAppContext.class
  • undertow: Servlet.class, Undertow.class, SslClientAuthMode.class

假如我們想換成undertow伺服器,依賴改了就行了

<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-undertow</artifactId>
</dependency>

假如我們想換成jetty伺服器,同理

<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>
  • 思維拉回來,工廠有了,我們看看工廠怎麼生成的webServer
TomcatServletWebServerFactory.java
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
        Registry.disableRegistry();
    }
    Tomcat tomcat = new Tomcat();
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    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);
    return getTomcatWebServer(tomcat);
}
  • 這個方法我們就不分析了,就是生成tomcat伺服器,和spring關聯不大,改天我們專門分析tomcat原始碼
  • 至此,整個springboot的載入web伺服器過程就完了

 豐極

歡迎關注微信公眾號:豐極,更多技術學習分享。

相關文章