九、SpringBoot整合Tomcat(源码分析)

Springboot内置了web的starter,开箱即用。web容器除了Tomcat还可以是Jetty等,因此SpringBoot内部肯定进行了封装处理,本文将从Tomcat作为入口,分析一下SpringBoot是如何整合Tomcat。

1. Spring 使用Tomcat

Spring上如果要使用Tomcat,类似一下例子,在Tomcat中增加DispatcherServlet,Servlet在执行的时候会调用init的方法。DispatcherServlet内部关联了ApplicationContext,所以可以在init的方法内做一下Spring IOC的一些整合逻辑。

java 复制代码
public class WebApplicationStarter {

   private Tomcat tomcat;

   public static void main(String[] args) throws LifecycleException {
      WebApplicationStarter starter = new WebApplicationStarter();
      starter.start();
   }

   public WebApplicationStarter(){
      tomcat = new Tomcat();
      tomcat.setPort(8080);

      tomcat.getConnector();
      Context context = tomcat.addContext("", null);

      try {
         DispatcherServlet dispatcherServlet = new DispatcherServlet(createContext(context.getServletContext()));
         Wrapper wrapper = Tomcat.addServlet(context, "dispatcherServlet", dispatcherServlet);
         wrapper.setLoadOnStartup(1);
         wrapper.addMapping("/*");

      }catch (Exception e) {
        
      }

   }

   public void start() throws LifecycleException {
      if (this.tomcat != null) {
         this.tomcat.start();
      }
   }

   public WebApplicationContext createContext(ServletContext context){
      AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
      applicationContext.register(WebApplicationConfig.class);
      applicationContext.setServletContext(context);
      applicationContext.refresh();
      applicationContext.registerShutdownHook();
      return applicationContext;
   }
}
java 复制代码
@Configuration
@ComponentScan(basePackageClasses = WebApplicationConfig.class)
@EnableWebMvc
public class WebApplicationConfig implements WebMvcConfigurer {

   @Autowired(required = false)
   private List<HandlerInterceptor> handlerInterceptors;

   @Override
   public void addInterceptors(InterceptorRegistry registry) {
      if (handlerInterceptors != null) {
         for (HandlerInterceptor interceptor : handlerInterceptors) {
            registry.addInterceptor(interceptor);
         }
      }
   }
}

2. Spring Boot内嵌Tomcat

SpringBoot会根据web类型是Servlet类型,从而创建的ApplicationContext是AnnotationConfigServletWebServerApplicationContext,实际主要的处理逻辑在父类ServletWebServerApplicationContext上。

在onRefresh会先调用父类的方法,然后调用createWebServer创建WebServer。WebServer是web容器的抽象,具体实现有TomcatWebServer,NettyWebServer等。

onRefresh来自AbstractApplicationContext启动流程中的一部分,可以查看本人的另外一篇文章:浅析Spring启动流程

java 复制代码
//ServletWebServerApplicationContext#onRefresh
protected void onRefresh() {
   super.onRefresh();
   try {
      createWebServer();
   }
   catch (Throwable ex) {
      throw new ApplicationContextException("Unable to start web server", ex);
   }
}

当WebServer和ServletContext为空时(默认情况下为空),则会通过ServletWebServerFactory进行创建WebServer。以Tomcat为例,ServletWebServerFactory的实现为TomcatServletWebServerFactory。

java 复制代码
//ServletWebServerApplicationContext#createWebServer
private void createWebServer() {
   WebServer webServer = this.webServer;
   ServletContext servletContext = getServletContext();
   if (webServer == null && servletContext == null) {
      ServletWebServerFactory factory = getWebServerFactory();
      this.webServer = factory.getWebServer(getSelfInitializer());
   }
}

接下来就分析一下TomcatServletWebServerFactory主要的逻辑,内部会调用Tomcat的api来实现Tomcat的整合,关注核心部分。

Tomcat整体架构,可以查看本人的另外一篇文章:Tomcat整体架构

主要有两大逻辑:构建Connector;构建Context。至于getTomcatWebServer逻辑则很简单,就是new TomcatWebServer进行返回,后文再分析TomcatWebServer。

java 复制代码
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 connector = new Connector(this.protocol);
   connector.setThrowOnFailure(true);
   tomcat.getService().addConnector(connector);
   //定制Connector
   customizeConnector(connector);
   tomcat.setConnector(connector);
   tomcat.getHost().setAutoDeploy(false);
   configureEngine(tomcat.getEngine());
   for (Connector additionalConnector : this.additionalTomcatConnectors) {
      tomcat.getService().addConnector(additionalConnector);
   }
   //准备Context
   prepareContext(tomcat.getHost(), initializers);
   //创建TomcatWebServer
   return getTomcatWebServer(tomcat);
}

2.1 构建Connector

首先会创建一个Connector对象,默认是org.apache.coyote.http11.Http11NioProtocol,Tomcat内部会直接使用这个类,作为Tomcat连接器的处理,支持Http/1.1协议,采用NIO模型。

紧接着就会对Connector进行定制。会设置端口号、属性值、URIEncoding、SSL以及UpgradeProtocol等处理。然后还会对ProtocolHandler和Connector进行定制。

  • 可以通过TomcatProtocolHandlerCustomizer对ProtocolHandler进行定制,如设置连接相关的参数:maxKeepAliveRequests等。
  • 可以通过TomcatConnectorCustomizer对Connector进行定制。
java 复制代码
//TomcatServletWebServerFactory#customizeConnector
protected void customizeConnector(Connector connector) {
   int port = Math.max(getPort(), 0);
   connector.setPort(port);
   if (StringUtils.hasText(this.getServerHeader())) {
      connector.setAttribute("server", this.getServerHeader());
   }
   if (connector.getProtocolHandler() instanceof AbstractProtocol) {
      customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
   }
   //对ProtocolHandler进行定制
   invokeProtocolHandlerCustomizers(connector.getProtocolHandler());
   if (getUriEncoding() != null) {
      connector.setURIEncoding(getUriEncoding().name());
   }
   // Don't bind to the socket prematurely if ApplicationContext is slow to start
   connector.setProperty("bindOnInit", "false");
   if (getSsl() != null && getSsl().isEnabled()) {
      customizeSsl(connector);
   }
   TomcatConnectorCustomizer compression = new CompressionConnectorCustomizer(getCompression());
   compression.customize(connector);
   //对Connector进行定制
   for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
      customizer.customize(connector);
   }
}

除此之外,还可以额外设置其他Connector。

java 复制代码
//TomcatServletWebServerFactory#getWebServer
for (Connector additionalConnector : this.additionalTomcatConnectors) {
   tomcat.getService().addConnector(additionalConnector);
}

2.2 构建Context

首先会创建TomcatEmbeddedContext对象,该对象继承StandardContext实现了Context接口(Tomcat提供),然后会做一些系列属性设置。同时这个Context也会加入到Host中,然后会对Context进行配置。

java 复制代码
//TomcatServletWebServerFactory#prepareContext
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());
   //...
   context.addLifecycleListener(new StaticResourceConfigurer(context));
   ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
   // 添加到Host
   host.addChild(context);
   //配置Context
   configureContext(context, initializersToUse);
   postProcessContext(context);
}

会添加Starter到Context中,而这里的Starter内部实际持有ServletContextInitializer接口(有onStartup方法,参数对象为ServletContext),这是一种扩展机制,可以通过定制ServletContextInitializer,实现对应的功能,比如,将DispatcherServlet通过ServletContext加入到Tomcat中(将Servlet加入到Tomcat中),后文将会分析。

此处,可以通过TomcatContextCustomizer接口,对Context进行定制处理。

java 复制代码
//TomcatServletWebServerFactory#configureContext
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
   TomcatStarter starter = new TomcatStarter(initializers);
   if (context instanceof TomcatEmbeddedContext) {
      TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
      embeddedContext.setStarter(starter);
      embeddedContext.setFailCtxIfServletStartFails(true);
   }
   //将TomcatStarter加入到context中。
   context.addServletContainerInitializer(starter, NO_CLASSES);
   //...
   //配置Session
   configureSession(context);
   new DisableReferenceClearingContextCustomizer().customize(context);
   //定制TomcatContextCustomizer
   for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
      customizer.customize(context);
   }
}

前面我们提到会把TomcatStarter(实现了ServletContainerInitializer接口)加入到Context。

在Context启动的时候进行,会调用ServletContainerInitializer。

java 复制代码
//StandardContext#startInternal
// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
    initializers.entrySet()) {
    try {
        entry.getKey().onStartup(entry.getValue(),
                getServletContext());
    } catch (ServletException e) {
        log.error(sm.getString("standardContext.sciFail"), e);
        ok = false;
        break;
    }
}

而在SpringBoot中的TomcatStarter类的onStartup方法中,实际上会循环调用ServletContextInitializer(注意,这两个接口不一样)

java 复制代码
//TomcatStarter#onStartup
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
   for (ServletContextInitializer initializer : this.initializers) {
      initializer.onStartup(servletContext);
   }
}

而在SpringBoot中,通过RegistrationBean作为基类实现ServletContextInitializer接口,对应的子类有ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean等。以Dispactcher为例,查看如何被添加到Context中。

首先基类实现了onStartup方法,然后提供了抽象方法register

java 复制代码
//RegistrationBean#onStartup
public final void onStartup(ServletContext servletContext) throws ServletException {
   String description = getDescription();
   if (!isEnabled()) {
      logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
      return;
   }
   register(description, servletContext);
}

子类DynamicRegistrationBean提供了addRegistration抽象方法

java 复制代码
//DynamicRegistrationBean#register
protected final void register(String description, ServletContext servletContext) {
   D registration = addRegistration(description, servletContext);
   if (registration == null) {
      logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
      return;
   }
   configure(registration);
}

子类ServletRegistrationBean会调用ServletContext的addServlet把servlet加入到Context中。DispatcherServletRegistrationBean类具化了servlet为DispatcherServlet

java 复制代码
//ServletRegistrationBean#addRegistration
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
   String name = getServletName();
   return servletContext.addServlet(name, this.servlet);
}

2.3 创建TomcatWebServer

创建TomcatWebServer的时候,会调用初始化方法

java 复制代码
//TomcatWebServer
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
   this.tomcat = tomcat;
   this.autoStart = autoStart;
   initialize();
}

初始化方法中,会调用Tomcat的start方法,在Context启动的时候,会先移除Connector(Context容器的启动在Connector之前),这样是避免Spring容器启动阶段,就开始处理连接相关内容。最后会启动一个非守护线程,避免应用启动后就直接退出了。

java 复制代码
private void initialize(){

	Context context = findContext();
	
	context.addLifecycleListener((event) -> {
		if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
			// Remove service connectors so that protocol binding doesn't
			// happen when the service is started.
			removeServiceConnectors();
		}
	}
	);
	// Start the server to trigger initialization listeners
	this.tomcat.start();
	// We can re-throw failure exception directly in the main thread
	rethrowDeferredStartupExceptions();
	// Unlike Jetty, all Tomcat threads are daemon threads. We create a
	// blocking non-daemon to stop immediate shutdown
	startDaemonAwaitThread();
}

2.4 WebServer启动

在Spring容器启动完后(会执行finishRefresh方法),此处会尝试启动WebServer

java 复制代码
//ServletWebServerApplicationContext#finishRefresh
protected void finishRefresh() {
   super.finishRefresh();
   WebServer webServer = startWebServer();
   if (webServer != null) {
      publishEvent(new ServletWebServerInitializedEvent(webServer, this));
   }
}

在start方法中,会把前面移除的Connector重新添加到Tomcat中,当Tomcat启动后,如果添加Connector,则会直接调用Connector的start方法,这也是为什么没有显示看到Connector的启动。然后会对Container重新进行load;最后会检查Connector的状态是否成功。

java 复制代码
//TomcatWebServer#start
public void start() {
	addPreviouslyRemovedConnectors();
	Connector connector = this.tomcat.getConnector();
	if (connector != null && this.autoStart) {
		performDeferredLoadOnStartup();
	}
	checkThatConnectorsHaveStarted();
}

2.5 自动配置

TomcatServletWebServerFactory

TomcatServletWebServerFactory会在ServletWebServerFactoryAutoConfiguration通过@Import(ServletWebServerFactoryConfiguration.EmbeddedTomcat)进行创建,EmbeddedTomcat就会创建TomcatServletWebServerFactory。创建的时候,会添加TomcatConnectorCustomizer、TomcatContextCustomizer、TomcatProtocolHandlerCustomizer这几个定制器。

java 复制代码
//ServletWebServerFactoryConfiguration$EmbeddedTomcat
@Configuration(proxyBeanMethods = false)
@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();
      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;
   }

}

DispatcherServlet

前面分析到,实际上是DispatcherServletRegistrationBean将DispatcherServlet添加到Context,所以肯定会有DispatcherServletRegistrationBean的创建。这里会@Import(DispatcherServletConfiguration),会对DispatcherServlet进行创建。

java 复制代码
@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {

   @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
   @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
   public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
         WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
      DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
            webMvcProperties.getServlet().getPath());
      registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
      registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
      multipartConfig.ifAvailable(registration::setMultipartConfig);
      return registration;
   }

}

DispatcherServlet创建后,还会根据web的一些配置,进行设置属性。

java 复制代码
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
protected static class DispatcherServletConfiguration {

   @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
   public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
      DispatcherServlet dispatcherServlet = new DispatcherServlet();
      dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
      dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
      dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
      dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
      dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
      return dispatcherServlet;
   }

   @Bean
   @ConditionalOnBean(MultipartResolver.class)
   @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
   public MultipartResolver multipartResolver(MultipartResolver resolver) {
      // Detect if the user has created a MultipartResolver but named it incorrectly
      return resolver;
   }

}

3. 参考资料

  1. SpringBoot源码2.2.x分支。
相关推荐
用户298698530148 分钟前
.NET 文档自动化:Spire.Doc 设置奇偶页页眉/页脚的最佳实践
后端·c#·.net
码路飞36 分钟前
GPT-5.3 Instant 终于学会好好说话了,顺手对比了下同天发布的 Gemini 3.1 Flash-Lite
java·javascript
序安InToo39 分钟前
第6课|注释与代码风格
后端·操作系统·嵌入式
xyy12339 分钟前
C#: Newtonsoft.Json 到 System.Text.Json 迁移避坑指南
后端
洋洋技术笔记41 分钟前
Spring Boot Web MVC配置详解
spring boot·后端
JxWang0542 分钟前
VS Code 配置 Markdown 环境
后端
navms1 小时前
搞懂线程池,先把 Worker 机制啃明白
后端
JxWang051 小时前
离线数仓的优化及重构
后端
Nyarlathotep01131 小时前
gin01:初探gin的启动
后端·go
JxWang051 小时前
安卓手机配置通用多屏协同及自动化脚本
后端