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进行管理。

 

©️2020 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值