SpringBoot(19)之嵌入式Servlet容器的自动配置源码分析

SpringBoot(19)之嵌入式Servlet容器的自动配置源码分析

微信搜索 zze_coding 或扫描 👉 二维码关注我的微信公众号获取更多资源推送:

依旧是从嵌入式 Servlet 容器的自动配置类看起:

// org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {
    @Configuration
    @ConditionalOnClass({ Servlet.class, Tomcat.class }) // 判断当前环境是否引入了嵌入式 Tomcat 依赖
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) // 当 IoC 容器中没有自定义的嵌入式 Servlet 容器工厂下方代码才生效
    public static class EmbeddedTomcat {
        @Bean
        public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
            return new TomcatEmbeddedServletContainerFactory();
        }
    }

    @Configuration
    @ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
            WebAppContext.class })  // 判断当前环境是否引入了嵌入式 Jetty 依赖
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) // 当 IoC 容器中没有自定义的嵌入式 Servlet 容器工厂下方代码才生效
    public static class EmbeddedJetty {
        @Bean
        public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
            return new JettyEmbeddedServletContainerFactory();
        }
    }

    @Configuration
    @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class }) // 判断当前环境是否引入了嵌入式 Undertow 依赖
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) // 当 IoC 容器中没有自定义的嵌入式 Servlet 容器工厂下方代码才生效
    public static class EmbeddedUndertow {
        @Bean
        public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
            return new UndertowEmbeddedServletContainerFactory();
        }
    }

    public static class BeanPostProcessorsRegistrar
            implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

        private ConfigurableListableBeanFactory beanFactory;

        @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,
                    "embeddedServletContainerCustomizerBeanPostProcessor",
                    EmbeddedServletContainerCustomizerBeanPostProcessor.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);
            }
        }

    }
}

切换容器

很明了,自动配置类中使用三个静态内部类来分别注册不同的嵌入式的 Servlet 容器工厂,按顺序从上到下分别为 Tomcat、Jetty、Undertow。具体要注册哪个 Servlet 容器工厂需要根据当前环境的依赖来决定,如果当前环境只引入了 Tomcat 场景依赖,那么就仅仅会注册 Tomcat 的容器工厂,其它两个 Servlet 容器工厂就不会被注册。这就是我们引入依赖便能切换 Servlet 容器的原因。

容器创建与启动

现在可以知道的是,在自动配置类中只是注册了一个嵌入式 Servlet 容器的工厂 bean,而并不是注册了嵌入式 Servlet 容器的实例 bean,顾名思义,嵌入式 Servlet 容器的工厂肯定是用来创建嵌入式 Servlet 容器的实例,那么这个嵌入式 Servlet 容器的实例是在何时被创建和启动的呢?

以默认的 Tomcat 容器为例,当我们启动一个 SpringBoot 程序时,我们会发现嵌入式 Tomcat 会随之启动,我们直接从 SpringBoot 程序的入口 run 方法开始看起:

// org.springframework.boot.SpringApplication#run
public static ConfigurableApplicationContext run(Object source, String... args) {
    return run(new Object[] { source }, args);
}

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return new SpringApplication(sources).run(args);
}

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    FailureAnalyzers analyzers = null;
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        analyzers = new FailureAnalyzers(context);
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        // <1>
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        listeners.finished(context, null);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, analyzers, ex);
        throw new IllegalStateException(ex);
    }
}

依次执行这几个 run 方法,接着进到 <1> 处的 refreshContext(context) 方法:

// org.springframework.boot.SpringApplication#refreshContext
private void refreshContext(ConfigurableApplicationContext context) {
    // <2>
    refresh(context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}

接着执行 <2> 处的 refresh(context) 方法:

// org.springframework.boot.SpringApplication#refresh
protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    ((AbstractApplicationContext) applicationContext).refresh();
}

继续到 ((AbstractApplicationContext) applicationContext).refresh() 方法:

// org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#refresh
@Override
public final void refresh() throws BeansException, IllegalStateException {
    try {
        super.refresh();
    }
    catch (RuntimeException ex) {
        stopAndReleaseEmbeddedServletContainer();
        throw ex;
    }
}

调用父类的 refresh() 方法:

// org.springframework.context.support.AbstractApplicationContext#refresh
public void refresh() throws BeansException, IllegalStateException {
    synchronized(this.startupShutdownMonitor) {
        this.prepareRefresh();
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
        this.prepareBeanFactory(beanFactory);

        try {
            this.postProcessBeanFactory(beanFactory);
            this.invokeBeanFactoryPostProcessors(beanFactory);
            this.registerBeanPostProcessors(beanFactory);
            this.initMessageSource();
            this.initApplicationEventMulticaster();
            // <3>
            this.onRefresh();
            this.registerListeners();
            this.finishBeanFactoryInitialization(beanFactory);
            this.finishRefresh();
        } catch (BeansException var9) {
            if (this.logger.isWarnEnabled()) {
                this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
            }

            this.destroyBeans();
            this.cancelRefresh(var9);
            throw var9;
        } finally {
            this.resetCommonCaches();
        }

    }
}

<3> 处又调用了子类的 onRefresh() 方法:

// org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#onRefresh
@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        createEmbeddedServletContainer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start embedded container",
                ex);
    }
}

根据方法名可以看出,接着会通过 createEmbeddedServletContainer()` 方法创建嵌入式 Servlet 容器:

// org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#createEmbeddedServletContainer
private void createEmbeddedServletContainer() {
    EmbeddedServletContainer localContainer = this.embeddedServletContainer;
    ServletContext localServletContext = getServletContext();
    if (localContainer == null && localServletContext == null) {
        // <4>
        EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
        // <5>
        this.embeddedServletContainer = containerFactory
                .getEmbeddedServletContainer(getSelfInitializer());
    }
    else if (localServletContext != null) {
        try {
            getSelfInitializer().onStartup(localServletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context",
                    ex);
        }
    }
    initPropertySources();
}

<4> 处通过 getEmbeddedServletContainerFactory() 获取到嵌入式 Servlet 容器工厂 bean,该 bean 在嵌入式 Servlet 容器自动配置类中就已经被注册,此时为 Tomcat 的工厂即: org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory,然后在 <5> 处通过 containerFactory.getEmbeddedServletContainer(getSelfInitializer()) 获取嵌入式 Servlet 容器的实例即 Tomcat 容器实例:

// org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory#getEmbeddedServletContainer
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
        ServletContextInitializer... initializers) {
    // 创建一个 Tomcat 实例
    Tomcat tomcat = new Tomcat();
    // 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);
    // <6> 传入配置完成的 Tomcat 实例
    return getTomcatEmbeddedServletContainer(tomcat);
}

<6> 处将完成基本配置的 Tomcat 实例传递给了 getTomcatEmbeddedServletContainer(tomcat) 方法:

// org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory#getTomcatEmbeddedServletContainer
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
        Tomcat tomcat) {
    return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
}

接着将指定的端口不小于 0 的判断结果作为是否自动启动的参数传递给了 Tomcat 容器类的构造方法,创建 Tomcat 容器实例:

// org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer#TomcatEmbeddedServletContainer(org.apache.catalina.startup.Tomcat, boolean)
public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
    Assert.notNull(tomcat, "Tomcat Server must not be null");
    this.tomcat = tomcat;
    this.autoStart = autoStart;
    // <7>
    initialize();
}

又在 <7> 处执行了 initialize()

// org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer#initialize
private void initialize() throws EmbeddedServletContainerException {
    TomcatEmbeddedServletContainer.logger
            .info("Tomcat initialized with port(s): " + getPortsDescription(false));
    synchronized (this.monitor) {
        try {
            addInstanceIdToEngineName();
            try {
                final Context context = findContext();
                context.addLifecycleListener(new LifecycleListener() {

                    @Override
                    public void lifecycleEvent(LifecycleEvent event) {
                        if (context.equals(event.getSource())
                                && Lifecycle.START_EVENT.equals(event.getType())) {
                            removeServiceConnectors();
                        }
                    }

                });
                // <8>
                this.tomcat.start();

                rethrowDeferredStartupExceptions();

                try {
                    ContextBindings.bindClassLoader(context, getNamingToken(context),
                            getClass().getClassLoader());
                }
                catch (NamingException ex) {
                }

                startDaemonAwaitThread();
            }
            catch (Exception ex) {
                containerCounter.decrementAndGet();
                throw ex;
            }
        }
        catch (Exception ex) {
            stopSilently();
            throw new EmbeddedServletContainerException(
                    "Unable to start embedded Tomcat", ex);
        }
    }
}

<8> 处通过 Tomcat 实例的 start() 方法启动了 Tomcat。

至此我们可以得出结论,随着 SpringBoot 程序的启动,SpringBoot 会使用注册的嵌入式 Servlet 容器工厂 bean 来创建嵌入式 Servlet 容器,接着会随着容器的创建来启动嵌入式 Servlet 容器。

容器配置的生效

通过上面的学习我们已经知道,可以通过修改配置文件及编写容器的定制器来修改嵌入式 Servlet 容器的配置,这些配置是如何生效的呢?

回到嵌入式 Servlet 容器的自动配置类中,我们会发现在该类上有一个 @Import(BeanPostProcessorsRegistrar.class) 注解,该注解是用来快速注册指定组件的,具体使用可参考【Import-快速注册】。查看该类的 registerBeanDefinitions 方法:

// org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration.BeanPostProcessorsRegistrar#registerBeanDefinitions
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
        BeanDefinitionRegistry registry) {
    if (this.beanFactory == null) {
        return;
    }
    // <1>
    registerSyntheticBeanIfMissing(registry,
            "embeddedServletContainerCustomizerBeanPostProcessor",
            EmbeddedServletContainerCustomizerBeanPostProcessor.class);
    registerSyntheticBeanIfMissing(registry,
            "errorPageRegistrarBeanPostProcessor",
            ErrorPageRegistrarBeanPostProcessor.class);
}

<1> 处可以看到注册了一个 embeddedServletContainerCustomizerBeanPostProcessor 即后置处理器组件,后置处理器使用可参考:【实现BeanPostProcessor接口】,查看该组件:

// org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor
package org.springframework.boot.context.embedded;

public class EmbeddedServletContainerCustomizerBeanPostProcessor
        implements BeanPostProcessor, BeanFactoryAware {

    private ListableBeanFactory beanFactory;

    private List<EmbeddedServletContainerCustomizer> customizers;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
                "EmbeddedServletContainerCustomizerBeanPostProcessor can only be used "
                        + "with a ListableBeanFactory");
        this.beanFactory = (ListableBeanFactory) beanFactory;
    }

    // <2> - begin 初始化之前执行
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        if (bean instanceof ConfigurableEmbeddedServletContainer) {
            postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
        }
        return bean;
    }
    // <2> - end

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        return bean;
    }

    // <3>
    private void postProcessBeforeInitialization(
            ConfigurableEmbeddedServletContainer bean) {
        // 遍历所有的容器定制器
        for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
            customizer.customize(bean);
        }
    }
    
    // 获取所有的容器定制器
    private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
        if (this.customizers == null) {
            // Look up does not include the parent context
            this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
                    this.beanFactory
                            .getBeansOfType(EmbeddedServletContainerCustomizer.class,
                                    false, false)
                            .values());
            Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
            this.customizers = Collections.unmodifiableList(this.customizers);
        }
        return this.customizers;
    }

}

查看 <2> 处的 postProcessBeforeInitialization 方法,该方法会在 IoC 容器创建一个 bean 实例后、初始化之前执行。在该方法中判断了当前创建的 bean 是不是嵌入式 Servlet 容器,如果是,则通过 <3> 处的 postProcessBeforeInitialization 方法,在该方法中遍历所有的容器定制器,通过容器定制器的 customize 方法来配置当前创建的 bean 即当前创建的嵌入式 Servlet 容器。因为在配置文件中做容器相关配置实际也是通过容器定制器来配置容器,所以修改配置文件及编写容器的定制器来修改容器配置会对当前 IoC 容器中所有的嵌入式 Servlet 容器生效。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.zze.xyz/archives/springboot19.html

Buy me a cup of coffee ☕.