SpringBoot(五) — 嵌入式Tomcat

​ 主要分析了嵌入式 Tomcat 的构建流程,重点对比了它和传统 Tomcat 启动方式的差异,也梳理了 SpringBoot 提供的相关扩展点,比如 ServletContextInitializerBeans 的处理逻辑。最后结合 Tomcat 启动和 Spring 单例 Bean 初始化的整体流程,对延迟绑定端口,以及整个启动顺序背后的依赖关系做了系统性的分析和思考

Tomcat自动配置

​ Spring Boot 的 spring-boot-starter-web 模块默认会引入自动配置、Spring MVC,以及最关键的嵌入式 Servlet 容器(Tomcat)依赖。其中,ServletWebServerFactoryAutoConfiguration 是嵌入式容器自动配置的核心类,其定义如下:

@AutoConfiguration
@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 {
  // ... 省略
}

​ 这个配置类导入了ServletWebServerFactoryConfiguration.EmbeddedTomcat,其定义如下:

@Configuration(proxyBeanMethods = false)
// Servlet和Tomcat相关class必须存在
@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();
        // 收集程序中自定义的 三个Tomcat组件:Connector,Context,ProtocolHandler 的后置处理器
        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;
    }

}

​ EmbeddedTomcat核心是注册一个 TomcatServletWebServerFactory 单例 Bean,该 Bean 是 SpringBoot 中创建嵌入式 Tomcat 的关键工厂类

Tomcat构建

容器refresh时,在内部的onRefresh是正式创建和启动Tomcat的时机(这时还未实例化容器中的所有单例bean),创建Tomcat的具体方法为TomcatServletWebServerFactory#getWebServer,其被调用链路为:

  1. ServletWebServerApplicationContext#onRefresh
  2. ServletWebServerApplicationContext#createWebServer

核心代码

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
        implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {

    public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
        // 创建Tomcat
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        for (LifecycleListener listener : this.serverLifecycleListeners) {
            tomcat.getServer().addLifecycleListener(listener);
        }
        // 创建Connector组件
        Connector connector = new Connector(this.protocol);
        connector.setThrowOnFailure(true);
        // 内部会创建Service组件
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);
        // 内部会创建Host组件,并设置autoDeploy=false,不进行webapps的部署
        tomcat.getHost().setAutoDeploy(false);
        // 内部会创建Engine
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers);
        return getTomcatWebServer(tomcat);
    }

    protected void customizeConnector(Connector connector) {
        int port = Math.max(getPort(), 0);
        connector.setPort(port);
        if (StringUtils.hasText(getServerHeader())) {
            connector.setProperty("server", getServerHeader());
        }
        if (connector.getProtocolHandler() instanceof AbstractProtocol) {
            customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
        }
        // 容器里的TomcatProtocolHandlerCustomizer bean 调用,后置处理ProtocolHandler组件
        invokeProtocolHandlerCustomizers(connector.getProtocolHandler());
        if (getUriEncoding() != null) {
            connector.setURIEncoding(getUriEncoding().name());
        }
        // 重要:bindOnInit设为false,在Tomcat的init阶段不会将Connector与端口进行绑定
        connector.setProperty("bindOnInit", "false");
        if (getHttp2() != null && getHttp2().isEnabled()) {
            connector.addUpgradeProtocol(new Http2Protocol());
        }
        if (getSsl() != null && getSsl().isEnabled()) {
            customizeSsl(connector);
        }
        TomcatConnectorCustomizer compression = new CompressionConnectorCustomizer(getCompression());
        compression.customize(connector);
        // 容器里的TomcatConnectorCustomizer bean调用,后置处理8080端口的Connector组件
        for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
            customizer.customize(connector);
        }
    }

    protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
        File documentRoot = getValidDocumentRoot();
        TomcatEmbeddedContext context = new TomcatEmbeddedContext();
        if (documentRoot != null) {
            context.setResources(new LoaderHidingResourceRoot(context));
        }
        context.setName(getContextPath());
        context.setDisplayName(getDisplayName());
        context.setPath(getContextPath());
        File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
        context.setDocBase(docBase.getAbsolutePath());
        context.addLifecycleListener(new FixContextListener());
        // 重要,将当前ClassLoader设置到Context里,作为后续TomcatEmbeddedWebappClassLoader的parentClassLoader
        context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
                : ClassUtils.getDefaultClassLoader());
        resetDefaultLocaleMapping(context);
        addLocaleMappings(context);
        try {
            context.setCreateUploadTargets(true);
        }
        catch (NoSuchMethodError ex) {
            // Tomcat is < 8.5.39. Continue.
        }
        configureTldPatterns(context);
        WebappLoader loader = new WebappLoader();
        loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
        // TomcatEmbeddedWebappClassLoader直接委派给父类加载class
        loader.setDelegate(true);
        context.setLoader(loader);
        if (isRegisterDefaultServlet()) {
            addDefaultServlet(context);
        }
        if (shouldRegisterJspServlet()) {
            addJspServlet(context);
            addJasperInitializer(context);
        }
        context.addLifecycleListener(new StaticResourceConfigurer(context));
        ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
        host.addChild(context);
        configureContext(context, initializersToUse);
        postProcessContext(context);
    }

    protected void configureContext(Context context, ServletContextInitializer[] initializers) {
        // 将ServletContextInitializer合起来包装为TomcatStarter这个javax.servlet.ServletContainerInitializer
        TomcatStarter starter = new TomcatStarter(initializers);
        if (context instanceof TomcatEmbeddedContext) {
            TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
            embeddedContext.setStarter(starter);
            embeddedContext.setFailCtxIfServletStartFails(true);
        }
        // 重要,后续的DispatcherServlet会在里面注册到Context
        context.addServletContainerInitializer(starter, NO_CLASSES);
        for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) {
            context.addLifecycleListener(lifecycleListener);
        }
        for (Valve valve : this.contextValves) {
            context.getPipeline().addValve(valve);
        }
        for (ErrorPage errorPage : getErrorPages()) {
            org.apache.tomcat.util.descriptor.web.ErrorPage tomcatErrorPage = new org.apache.tomcat.util.descriptor.web.ErrorPage();
            tomcatErrorPage.setLocation(errorPage.getPath());
            tomcatErrorPage.setErrorCode(errorPage.getStatusCode());
            tomcatErrorPage.setExceptionType(errorPage.getExceptionName());
            context.addErrorPage(tomcatErrorPage);
        }
        for (MimeMappings.Mapping mapping : getMimeMappings()) {
            context.addMimeMapping(mapping.getExtension(), mapping.getMimeType());
        }
        configureSession(context);
        configureCookieProcessor(context);
        new DisableReferenceClearingContextCustomizer().customize(context);
        for (String webListenerClassName : getWebListenerClassNames()) {
            context.addApplicationListener(webListenerClassName);
        }
        // Context组件配置完毕,可以调用容器里的TomcatContextCustomizer bean了,后置处理Context组件
        for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
            customizer.customize(context);
        }
    }
}

差异和拓展

​ SpringBoot以编程方式创建、配置和组合了 Tomcat 核心组件(Server、Service、Connector、Engine、Host、Context),不依赖 server.xmlwebapps 目录。

我们只需关注其嵌入式Tomcat最关键的部分:和传统tomcat启动的区别SpringBoot提供的拓展

差异

  1. 端口绑定延迟

    ​ AbstractEndpoint#bindOnInit=false,在Connector初始化阶段不会创建Socket和绑定8080端口

  2. 禁用自动部署机制

    ​ StandardHost#autoDeploy=false,则不再扫描和部署 webapps 目录、war包、xml文件

  3. 自定义类加载器

    ​ StandardContext#parentClassLoader被设置为 SpringBoot 提供的类加载器,如 fatJar 模式下的 LaunchedURLClassLoader,以支持class加载

  4. 类加载器委托优先级修改

    WebappLoader#delegate = true:让Tomcat的ParallelWebappClassLoader直接走父classLoader加载class(即StandardContext的parentClassLoader)

拓展

  1. Servlet 容器初始化器ServletContextInitializer

    ​ 所有的 ServletContextInitializer Bean 会被收集、包装成一个统一的 TomcatStarter 实例,并添加为StandardContext的ServletContainerInitializer,在 StandardContext#startInternal 阶段被调用。常见ServletContextInitializer实现类:

    • ServletRegistrationBean
    • FilterRegistrationBean
    • ServletListenerRegistrationBean
    • DelegatingFilterProxyRegistrationBean
  2. 三种Customizer后置处理(即等待对应的组件配置完毕再处理)

    • TomcatConnectorCustomizer:Connector组件
    • TomcatContextCustomizer:Context组件
    • TomcatProtocolHandlerCustomizer:ProtocolHandler组件

ServletContextInitializer处理

​ ServletWebServerApplicationContext#getSelfInitializer方法构建了一个 ServletContextInitializer 类型的函数式接口实例,其内部通过 ServletContextInitializerBeans 收集并统一处理所有容器中的 ServletContextInitializer Bean及进行web组件的兜底适配

​ DispatcherServletAutoConfiguration就是一个典型的例子,它是SpringBoot提供的自动配置类,负责实例化 DispatcherServlet,并通过 DispatcherServletRegistrationBean 将其注册到 StandardContext 中

ServletContextInitializerBeans

该类用于统一处理所有 ServletContextInitializer 类型的组件,其核心作用包括:

  • 收集:自动从容器中收集所有 ServletContextInitializer (DispatcherServletRegistrationBean)
  • 适配兜底:对于未直接实现 ServletContextInitializer的常见web组件Bean(如 ServletFilterEventListener),将其适配封装为相应的...RegistrationBean
  • 配置:应用 MultipartConfigElement Bean到所有 ServletRegistrationBean
  • 排序:对所有 ServletContextInitializer 进行排序,确保执行顺序可控
public class ServletContextInitializerBeans extends AbstractCollection<ServletContextInitializer> {

    private final MultiValueMap<Class<?>, ServletContextInitializer> initializers;
    // 最终排好序的ServletContextInitializer集合
    private List<ServletContextInitializer> sortedList;

    public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
            Class<? extends ServletContextInitializer>... initializerTypes) {
        this.initializers = new LinkedMultiValueMap<>();
        this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
                : Collections.singletonList(ServletContextInitializer.class);
        // 收集所有类型为 ServletContextInitializer 的 bean
        addServletContextInitializerBeans(beanFactory);
        // 适配其他剩下的web Bean组件(如 MultipartConfig、Servlet、Filter、Listener 等),适配成对应的ServletContextInitializer
        addAdaptableBeans(beanFactory);
        // ServletContextInitializer排序
        List<ServletContextInitializer> sortedInitializers = this.initializers.values()
            .stream()
            .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
            .collect(Collectors.toList());
        this.sortedList = Collections.unmodifiableList(sortedInitializers);
        // debug日志记录
        logMappings(this.initializers);
    }

    private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer,
            ListableBeanFactory beanFactory) {
        // 实例化ServletRegistrationBean
        if (initializer instanceof ServletRegistrationBean) {
            Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet();
            addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
        }
        // 处理FilterRegistrationBean
        else if (initializer instanceof FilterRegistrationBean) {
            Filter source = ((FilterRegistrationBean<?>) initializer).getFilter();
            addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
        }
        // 处理DelegatingFilterProxyRegistrationBean
        else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
            String source = ((DelegatingFilterProxyRegistrationBean) initializer).getTargetBeanName();
            addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
        }
        // 处理ServletListenerRegistrationBean
        else if (initializer instanceof ServletListenerRegistrationBean) {
            EventListener source = ((ServletListenerRegistrationBean<?>) initializer).getListener();
            addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);
        }
        else { // 其它
            addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory,
                    initializer);
        }
    }

    protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
        // 只获取一个MultipartConfigElement Bean,将其适配为ServletRegistrationBean
        MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
        // 获取未处理的Servlet Bean(即不是ServletRegistrationBean),将其适配为ServletRegistrationBean
        addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
        // 获取未处理的Filter Bean(即不是FilterRegistrationBean),将其适配为 FilterRegistrationBean
        addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
        // 其它Servlet相关的Listener,适配为ServletListenerRegistrationBean
        for (Class<?> listenerType : ServletListenerRegistrationBean.getSupportedTypes()) {
            addAsRegistrationBean(beanFactory, EventListener.class, (Class<EventListener>) listenerType,
                    new ServletListenerRegistrationBeanAdapter());
        }
    }
}

Tomcat启动

​ 在 TomcatServletWebServerFactory#getWebServer 方法中,最终会创建一个 TomcatWebServer 实例。在其构造方法中,会调用 TomcatWebServer#initialize 方法启动 Tomcat,但在启动前,Tomcat 会先注册一个 LifecycleListener。该监听器会在 Tomcatstart 阶段移除 Service 中的 Connector,以防止 Tomcat 过早绑定端口、过早接收请求

​ 随后,ServletWebServerFactory#getWebServer 方法返回,SpringBoot 立即注册一个 WebServerStartStopLifecycle Bean。这个 Bean 实现了 SmartLifecycle 接口,因此会在所有单例 Bean 完成初始化之后再触发其 start() 方法

WebServerStartStopLifecycle#start()方法会将之前移除的 Connector 重新注册回 Service,并真正启动 Connector,此时 Tomcat 才会绑定端口、开始对外提供服务

private void initialize() throws WebServerException {
    synchronized (this.monitor) {
        try {
            addInstanceIdToEngineName();

            Context context = findContext();
            // 添加一个LifecycleListener,职责为在tomcat的start阶段移除Service里的connector组件,避免过早绑定端口
            context.addLifecycleListener((event) -> {
                if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
                    removeServiceConnectors();
                }
            });

            // 启动tomcat
            this.tomcat.start();
            // ...省略
        } catch (Exception ex) { // ...省略
        }
    }
}

思考

端口绑定延迟

​ 从上面的分析可以看出,Tomcat 本身的启动是在所有单例 Bean 实例化之前完成的,但真正的端口绑定(也就是 Connector 的启动)却是在所有单例 Bean 实例化之后才进行的。

​ 这其实挺好理解的,因为一旦端口对外暴露,整个服务就应该是就绪的状态。如果 Spring 的 Bean 还没初始化完,结果服务先收到了请求,就会出问题

启动顺序

​ 那问题来了:为什么不干脆把 Tomcat 的启动放在最后,等所有单例 Bean 都初始化完了再启动?这样就不用搞什么移除 Connector 的骚操作了,流程也简单些。

这个其实可以从依赖关系的角度来理解,也就是拓扑排序的思路来看整个流程:

​ SpringBoot 提供了非常方便的方式来注册 Servlet、Filter 等 Web 组件,只要把它们声明成 Bean 就行了。但这些组件要想发挥作用,必须注册到 StandardContext。而 StandardContext 是嵌套在 Tomcat 启动过程中的,也就是说,Servlet 和 Filter 的初始化是依赖于 Tomcat 已经就绪的

​ 而我们都知道,Spring 的单例 Bean 初始化顺序是不可控的,如果你把 Tomcat 放在所有 Bean 都加载完再启动,那这些 Servlet、Filter 类型的 Bean 初始化时就找不到 StandardContext,初始化逻辑就会出错。所以核心逻辑其实很简单:既然 Servlet、Filter 等 Bean 依赖 Tomcat 的内部结构(比如 StandardContext),那就得确保 Tomcat 先初始化,Spring 在实例化相关 Bean 的时候才能正确地把这些组件注册进去

个人感想

​ 综上,这一连串看起来很复杂的操作背后,其实都有深刻的理由。你可能觉得自己能想出更好的方案,但那往往是因为没站在全局思考,想清楚整个系统的依赖链和所有可能的场景。

​ 但我们不用为此感到沮丧,因为我们在思考,我们在尝试,我们不满足于能跑就行,我们在努力从不同角度拆解系统的结构与逻辑。最后,我们才能真正的驾驭它

其它链接