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,其被调用链路为:
- ServletWebServerApplicationContext#onRefresh
- 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.xml 或 webapps 目录。
我们只需关注其嵌入式Tomcat最关键的部分:和传统tomcat启动的区别和SpringBoot提供的拓展
差异
端口绑定延迟
AbstractEndpoint#bindOnInit=false,在Connector初始化阶段不会创建Socket和绑定8080端口
禁用自动部署机制
StandardHost#autoDeploy=false,则不再扫描和部署
webapps目录、war包、xml文件自定义类加载器
StandardContext#parentClassLoader被设置为 SpringBoot 提供的类加载器,如 fatJar 模式下的 LaunchedURLClassLoader,以支持class加载
类加载器委托优先级修改
WebappLoader#delegate = true:让Tomcat的ParallelWebappClassLoader直接走父classLoader加载class(即StandardContext的parentClassLoader)
拓展
Servlet 容器初始化器:
ServletContextInitializer 所有的 ServletContextInitializer Bean 会被收集、包装成一个统一的
TomcatStarter实例,并添加为StandardContext的ServletContainerInitializer,在StandardContext#startInternal阶段被调用。常见ServletContextInitializer实现类:- ServletRegistrationBean
- FilterRegistrationBean
- ServletListenerRegistrationBean
- DelegatingFilterProxyRegistrationBean
三种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(如Servlet、Filter、EventListener),将其适配封装为相应的...RegistrationBean - 配置:应用
MultipartConfigElementBean到所有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。该监听器会在 Tomcat 的 start 阶段移除 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 的时候才能正确地把这些组件注册进去。
个人感想
综上,这一连串看起来很复杂的操作背后,其实都有深刻的理由。你可能觉得自己能想出更好的方案,但那往往是因为没站在全局思考,想清楚整个系统的依赖链和所有可能的场景。
但我们不用为此感到沮丧,因为我们在思考,我们在尝试,我们不满足于能跑就行,我们在努力从不同角度拆解系统的结构与逻辑。最后,我们才能真正的驾驭它