九、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分支。
相关推荐
编程修仙几秒前
Java线程常用的方法
java·开发语言
冰零(lane)6 分钟前
Spring Boot 动态数据源切换
java·数据库·spring boot
ps酷教程18 分钟前
springboot配置https,并使用wss
spring boot·后端·https
小馋喵知识杂货铺18 分钟前
XSS--跨站脚本攻击
java
hummhumm21 分钟前
第 38 章 -GO语言 事件驱动架构
java·javascript·后端·python·架构·golang·ruby
夏子曦39 分钟前
java——SpringBoot启动Tomcat的过程
java·spring boot·tomcat
吹老师个人app编程教学40 分钟前
JVM_栈详解一
java
济南小草根40 分钟前
参加面试被问到的面试题
java·面试
芸尚非43 分钟前
idea初始化设置
java·ide·intellij-idea
老胡说前端1 小时前
vue3 多种方式接受props,定义ref,reactive
java·前端·javascript