Spring Boot源碼(七) - 嵌入式Servlet服務器(Tomcat)

目錄

一、onRefresh(createWebServer)

1、獲取Servlet服務器工廠

2、根據不同Web類型的AbstractApplicationContext,對應不同的ServletContextInitializer以初始化準備

3、調用工廠方法生產Servlet服務器

4、在ConfigurableEnvironment中設置ServletContext屬性

二、finishRefresh(startWebServer)

1、啓動Tomcat服務

2、發送ServletWebServerInitializedEvent類型事件

三、總結


    在Web類型的Spring Boot項目中(非reactive項目),使用了嵌入式Servlet服務器,下面只分析Tomcat(由Apache官方提供,並非Spring Boot的產物),Jetty、Undertow類似就不進行分析了。之前的Spring Boot源碼分析得知,Web類型啓動的AbstractApplicationContext類型爲AnnotationConfigServletWebServerApplicationContext,而refresh模板方法的Onrefresh和finishRefresh是在其父類ServletWebServerApplicationContext中完成的。

    在onRefresh階段執行了createWebServer方法,初始化了Tomcat。在finishRefresh階段執行了startWebServer,以啓動Tomcat服務。在開始分析之前還是先看一下Tomcat的結構圖,不論嵌入式還是非嵌入式的Tomcat都一樣。因爲Tomcat源碼中就是以Tomcat、Server、Service、Engine等來表示的。

 

一、onRefresh(createWebServer)

    Spring Boot調用AbstractApplicationContext的refresh方法,通過refresh的invokeBeanFactoryPostProcessors處理了DeferredImportSelector類型,完成了自動裝配。會執行refresh的OnRefresh階段,整體流程可以參見SpringIoc源碼(一)- 總覽開始的所有博客。由於創建的AbstractApplicationContext類型不同,所以如果是AnnotationConfigApplicationContext類型則在onRefresh階段會什麼都不執行。如果爲AnnotationConfigServletWebServerApplicationContextAnnotationConfigReactiveWebServerApplicationContext類型則會重寫父類的方法,添加創建Servlet服務器(嵌入式)的邏輯。

     下面只分析AnnotationConfigServletWebServerApplicationContext類型,並且爲默認的 Tomcat服務器的情況。

@Override
protected void onRefresh() {
    // 父類該方法會初始化主題( UiApplicationContextUtils.initThemeSource(this); )
    super.onRefresh();
    try {   // 創建嵌入式Servlet服務器
        createWebServer();
    } catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}
private void createWebServer() {
    // 獲取當前的WebServer
    WebServer webServer = this.webServer;
    // 獲取當前的ServletContext
    ServletContext servletContext = getServletContext();
    // 默認都爲空,會進入這裏
    if (webServer == null && servletContext == null) {
        // 獲取Servlet服務器工廠
        ServletWebServerFactory factory = getWebServerFactory();
        // 工廠方法,獲取Servlet服務器,並作爲AbstractApplicationContext的一個屬性進行設置。
        this.webServer = factory.getWebServer(getSelfInitializer());
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        } catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }
    // 初始化一些ConfigurableEnvironment中的 ServletContext信息
    initPropertySources();
}

1、獲取Servlet服務器工廠

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("省略部分代碼");
    }
    if (beanNames.length > 1) {
        throw new ApplicationContextException(省略部分代碼);
    }
    return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

    直接從BeanFactory的中獲取ServletWebServerFactory類型的Bean,之前分析過getBean的流程(詳細可以參見:SpringIoc源碼(十五)- BeanFactory(四)- getBean(doGetBean上 - 緩存中獲取)開始的源碼)。

    在之前我們知道了自動裝配會加載META-INF/spring.factories中的配置,而org.springframework.boot.autoconfigure.EnableAutoConfiguration=中就有一項是org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration。可以先看看其結構(只看Tomcat部分的配置):

@Configuration(proxyBeanMethods = false)
@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 {
    // 其餘進行省略
}

    根據自動裝配的原則可以知,當前的@Configuration是否生效,需要:Class ServletRequest存在、並且當前爲ApplicationContext爲GenericWebApplicationContext類型(其實根據Spring Boot的webApplicationType就決定了);並且會加載ServletProperties的配置信息,即Tomcat等Servlet服務器的配置信息。最重要的是使用@Import的方式將下面的Class註冊爲Bean:

ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class

主要看EmbeddedTomcat內部類:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
    @Bean
    public 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;
    }
}

    只有存在Servlet類、Tomcat類,UpgradeProtocal類時;並且在當前容器中(不包含父容器),不存在ServletWebServerFactory類型或者其子類的Bean時,@Component才生效。那麼纔會將tomcatServletWebServerFactory方法的TomcatServletWebServerFactory 註冊成Bean。

    當然一般情況下默認只引入了Tomcat,可能存在Tomcat、jetty、undertow的Factory都存在的情況。其getWebServerFactory()方法會調用getBeanNamesForType進行獲取。由於在BeanFactory中獲取的數據是使用ConcurrentHashMap進行存在的,是不能保證有序的。但是默認取了第一個。所以比如我們要使用Jetty服務器時,只需要引入Jetty的starter包,並且可以將Tomcat的包給排掉即可。

 

2、根據不同Web類型的AbstractApplicationContext,對應不同的ServletContextInitializer以初始化準備

private ServletContextInitializer getSelfInitializer() {
    return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {

    // 檢查中的WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE屬性
    prepareWebApplicationContext(servletContext);

    // 創建ServletContextScope並且註冊成Bean
    registerApplicationScope(servletContext);
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);

    // 所有ServletContextInitializer 的onStartup方法回調
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);
    }
}

    做了一些準備和初始化工作,並完成了ServletContextInitializer 的onStartup回調。

 

3、調用工廠方法生產Servlet服務器

@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);
}

     直接new了Apache的Tomcat,並且設置了Connector等屬性。主要是處理了prepareContext、getTomcatWebServer。

Spring使用WebServer接口進行Tomcat、Jetty、Undertow等Servlet服務器的管理,使用TomcatWebServer對Tomcat進行管理,處理其start等。具體等研究完Tomcat源碼再來細分析。其管理關係如下:

public class TomcatWebServer implements WebServer {
    // Container的個數,使用AtomicInteger多線程計數器
    private static final AtomicInteger containerCounter = new AtomicInteger(-1);
    // 下面重要的方法都是使用synchronized(monitor)保證互斥
    private final Object monitor = new Object();
    // 保存內部的Service與多個Connector的關係
    private final Map<Service, Connector[]> serviceConnectors = new HashMap<>();
    // 這也就是Tomcat與Spring的關係; 作爲TomcatWebServer的一個屬性
    private final Tomcat tomcat;
    // 是否自動start
    private final boolean autoStart;
    // 使用volatile + synchronized(monitor)保證started線程安全的狀態;後者只保證原子性,
    // 前者保證有序和可見性
    private volatile boolean started;
}

 

4、在ConfigurableEnvironment中設置ServletContext屬性

@Override
protected void initPropertySources() {
    ConfigurableEnvironment env = getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, null);
    }
}
@Override
public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
    WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}
public static void initServletPropertySources(MutablePropertySources sources,
                                             @Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {

    Assert.notNull(sources, "'propertySources' must not be null");
    String name = StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME;
    if (servletContext != null && sources.contains(name) && sources.get(name) instanceof StubPropertySource) {
        sources.replace(name, new ServletContextPropertySource(name, servletContext));
    }
    name = StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME;
    if (servletConfig != null && sources.contains(name) && sources.get(name) instanceof StubPropertySource) {
        sources.replace(name, new ServletConfigPropertySource(name, servletConfig));
    }
}

將上面初始化的servletContext設置到ConfigurableWebEnvironment的MutablePropertySources中,其結構爲:private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();

    所以比較清楚了,之前分析refresh時指定Environment爲AbstractApplicationContext中的元素,通過Bean的生命週期等很多方式我們都可以拿到Environment,Web類型服務時,還可以通過Environment根據key(servletContextInitParamsservletConfigInitParams)獲取ServletContext、ServletConfig等信息。

 

二、finishRefresh(startWebServer)

@Override
protected void finishRefresh() {
    super.finishRefresh();
    WebServer webServer = startWebServer();
    if (webServer != null) {
        publishEvent(new ServletWebServerInitializedEvent(webServer, this));
    }
}

1、啓動Tomcat服務

private WebServer startWebServer() {
    WebServer webServer = this.webServer;
    if (webServer != null) {
        webServer.start();
    }
    return webServer;
}

 比如查看TomcatWebServer的start方法:

@Override
public void start() throws WebServerException {
    synchronized (this.monitor) {
        if (this.started) {
            return;
        }
        try {
            addPreviouslyRemovedConnectors();
            Connector connector = this.tomcat.getConnector();
            if (connector != null && this.autoStart) {
                performDeferredLoadOnStartup();
            }
            checkThatConnectorsHaveStarted();
            this.started = true;
        } // 省略部分異常代碼
    }
}

核心方法在獲取到Container 的啓動方法,

private void performDeferredLoadOnStartup() {
    try {
        for (Container child : this.tomcat.getHost().findChildren()) {
            if (child instanceof TomcatEmbeddedContext) {
                ((TomcatEmbeddedContext) child).deferredLoadOnStartup();
            }
        }
    } // 省略異常代碼
}
void deferredLoadOnStartup() throws LifecycleException {
    doWithThreadContextClassLoader(getLoader().getClassLoader(),
        () -> getLoadOnStartupWrappers(findChildren()).forEach(this::load));
}

    下面就是核心方法,即Tomcat的start方法,本質就是Runnable的run方法調用。

private void doWithThreadContextClassLoader(ClassLoader classLoader, Runnable code) {
    ClassLoader existingLoader = (classLoader != null) ? ClassUtils.overrideThreadContextClassLoader(classLoader)
            : null;
    try {
        code.run();
    }
    finally {
        if (existingLoader != null) {
            ClassUtils.overrideThreadContextClassLoader(existingLoader);
        }
    }
}

 

2、發送ServletWebServerInitializedEvent類型事件

如果想在Tomcat啓動成功後,這個時機處理事情(一般還可以使用其他的生命週期)。更多可能是可以獲取的對應的TomcatWebServer、或者Tomcat。判斷啓動的線程數,Tomcat連接池的核心參數等信息。

 

三、總結

1)、Spring Boot利用Spring的啓動的refresh模板方法,在onRefresh階段將WebServer進行創建。而在finishRefresh階段完成服務器的啓動,其本質就是Runnable的run方法的調用。

2)、創建Servlet服務器是利用的 Spring Boot的自動裝配,將ServletWebServerFactoryAutoConfiguration進行注入,其內部使用@Import將ServletWebServerFactoryConfiguration.EmbeddedTomcat等進行注入。只是在其注入時會根據多種@Conditional方式共同決定注入的服務器工廠類型,如:TomcatServletWebServerFactory。

3)、根據工廠生成Tomcat服務器,並且在Spring Boot中使用WebServer接口的實現TomcatWebServer對Tomcat進行管理。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章