对于一个 SpringBoot web 工程来说,一个主要的依赖标志就是有 spring-boot-starter-web 这个 starter ,spring-boot-starter-web 模块在 spring boot 中其实并没有代码存在,只是在 pom.xml 中携带了一些依赖,包括 web、webmvc、tomcat 等:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </dependency> </dependencies>
|
Spring Boot 默认的 web 服务容器是 tomcat ,如果想使用 Jetty 等来替换 Tomcat ,可以自行参考官方文档来解决。
web、webmvc、tomcat 等提供了 web 应用的运行环境,那 spring-boot-starter 则是让这些运行环境工作的开关(因为 spring-boot-starter 中会间接引入 spring-boot-autoconfigure )。
WebServer 自动配置
在 spring-boot-autoconfigure 模块中,有处理关于 WebServer 的自动配置类 ServletWebServerFactoryAutoConfiguration 。
ServletWebServerFactoryAutoConfiguration
代码片段如下:
1 2 3 4 5 6 7 8 9 10
| @Configuration @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
|
两个 Condition 表示当前运行环境是基于 servlet 标准规范的 web 服务:
- ConditionalOnClass(ServletRequest.class) : 表示当前必须有 servlet-api 依赖存在
- ConditionalOnWebApplication(type = Type.SERVLET) :仅基于servlet的Web应用程序
@EnableConfigurationProperties(ServerProperties.class):ServerProperties 配置中包括了常见的 server.port 等配置属性。
通过 @Import 导入嵌入式容器相关的自动配置类,有 EmbeddedTomcat、EmbeddedJetty 和EmbeddedUndertow。
综合来看,ServletWebServerFactoryAutoConfiguration 自动配置类中主要做了以下几件事情:
- 导入了内部类 BeanPostProcessorsRegistrar,它实现了 ImportBeanDefinitionRegistrar,可以实现ImportBeanDefinitionRegistrar 来注册额外的 BeanDefinition。
- 导入了 ServletWebServerFactoryConfiguration.EmbeddedTomcat 等嵌入容器先关配置(我们主要关注tomcat 相关的配置)。
- 注册了ServletWebServerFactoryCustomizer、TomcatServletWebServerFactoryCustomizer 两个WebServerFactoryCustomizer 类型的 bean。
下面就针对这几个点,做下详细的分析。
BeanPostProcessorsRegistrar
BeanPostProcessorsRegistrar 这个内部类的代码如下(省略了部分代码):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (this.beanFactory == null) { return; } registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class); registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class); } }
|
上面这段代码中,注册了两个 bean,一个 WebServerFactoryCustomizerBeanPostProcessor,一个 errorPageRegistrarBeanPostProcessor;这两个都实现类 BeanPostProcessor 接口,属于 bean 的后置处理器,作用是在 bean 初始化前后加一些自己的逻辑处理。
- WebServerFactoryCustomizerBeanPostProcessor:作用是在 WebServerFactory 初始化时调用上面自动配置类注入的那些 WebServerFactoryCustomizer ,然后调用 WebServerFactoryCustomizer 中的 customize 方法来 处理 WebServerFactory。
- errorPageRegistrarBeanPostProcessor:和上面的作用差不多,不过这个是处理 ErrorPageRegistrar 的。
下面简单看下 WebServerFactoryCustomizerBeanPostProcessor 中的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof WebServerFactory) { postProcessBeforeInitialization((WebServerFactory) bean); } return bean; } private void postProcessBeforeInitialization(WebServerFactory webServerFactory) { LambdaSafe .callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory) .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class) .invoke((customizer) -> customizer.customize(webServerFactory)); } }
|
自动配置类中注册的两个 Customizer Bean
这两个 Customizer 实际上就是去处理一些配置值,然后绑定到 各自的工厂类的。
WebServerFactoryCustomizer
将 serverProperties 配置值绑定给 ConfigurableServletWebServerFactory 对象实例上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Override public void customize(ConfigurableServletWebServerFactory factory) { PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(this.serverProperties::getPort).to(factory::setPort); map.from(this.serverProperties::getAddress).to(factory::setAddress); map.from(this.serverProperties.getServlet()::getContextPath) .to(factory::setContextPath); map.from(this.serverProperties.getServlet()::getApplicationDisplayName) .to(factory::setDisplayName); map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession); map.from(this.serverProperties::getSsl).to(factory::setSsl); map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp); map.from(this.serverProperties::getCompression).to(factory::setCompression); map.from(this.serverProperties::getHttp2).to(factory::setHttp2); map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader); map.from(this.serverProperties.getServlet()::getContextParameters) .to(factory::setInitParameters); }
|
TomcatServletWebServerFactoryCustomizer
相比于上面那个,这个 customizer 主要处理 Tomcat 相关的配置值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Override public void customize(TomcatServletWebServerFactory factory) { ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat(); if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) { factory.getTldSkipPatterns() .addAll(tomcatProperties.getAdditionalTldSkipPatterns()); } if (tomcatProperties.getRedirectContextRoot() != null) { customizeRedirectContextRoot(factory, tomcatProperties.getRedirectContextRoot()); } if (tomcatProperties.getUseRelativeRedirects() != null) { customizeUseRelativeRedirects(factory, tomcatProperties.getUseRelativeRedirects()); } }
|
WebServerFactory
用于创建 WebServer 的工厂的标记接口。
类体系结构

上图为 WebServerFactory -> TomcatServletWebServerFactory 的整个类结构关系。
TomcatServletWebServerFactory
TomcatServletWebServerFactory 是用于获取 Tomcat 作为 WebServer 的工厂类实现,其中最核心的方法就是 getWebServer,获取一个 WebServer 对象实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Override public WebServer getWebServer(ServletContextInitializer... initializers) { Tomcat tomcat = new 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); return getTomcatWebServer(tomcat); }
|
- 1、customizeConnector : 给 Connector 设置 port、protocolHandler、uriEncoding 等。Connector 构造的逻辑主要是在NIO和APR选择中选择一个协议,然后反射创建实例并强转为 ProtocolHandler
- 2、prepareContext 这里并不是说准备当前 Tomcat 运行环境的上下文信息,而是准备一个 StandardContext ,也就是准备一个 web app。
准备 Web App Context 容器
对于 Tomcat 来说,每个 context 就是映射到 一个 web app 的,所以 prepareContext 做的事情就是将 web 应用映射到一个 TomcatEmbeddedContext ,然后加入到 Host 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| 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()); context.setParentClassLoader( (this.resourceLoader != null) ? this.resourceLoader.getClassLoader() : ClassUtils.getDefaultClassLoader()); resetDefaultLocaleMapping(context); addLocaleMappings(context); context.setUseRelativeRedirects(false); try { context.setCreateUploadTargets(true); } catch (NoSuchMethodError ex) { } configureTldSkipPatterns(context); WebappLoader loader = new WebappLoader(context.getParentClassLoader()); loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName()); 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); }
|
从上面可以看下,WebappLoader 可以通过 setLoaderClass 和 getLoaderClass 这两个方法可以更改loaderClass 的值。所以也就意味着,我们可以自己定义一个继承 webappClassLoader 的类,来更换系统自带的默认实现。
初始化 TomcatWebServer
在 getWebServer 方法的最后就是构建一个 TomcatWebServer。
1 2 3 4 5 6 7 8 9 10 11 12 13
| protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { return new TomcatWebServer(tomcat, getPort() >= 0); }
public TomcatWebServer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; initialize(); }
|
这里主要是 initialize 这个方法,这个方法中将会启动 tomcat 服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| private void initialize() throws WebServerException { logger.info("Tomcat initialized with port(s): " + getPortsDescription(false)); synchronized (this.monitor) { try { addInstanceIdToEngineName(); Context context = findContext(); context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) { removeServiceConnectors(); } }); this.tomcat.start(); rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } catch (NamingException ex) { }
startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); throw new WebServerException("Unable to start embedded Tomcat", ex); } } }
|
查找 Context ,实际上就是查找一个Tomcat 中的一个 web 应用,SpringBoot 中默认启动一个 Tomcat ,并且一个 Tomcat 中只有一个 Web 应用(FATJAR 模式下,应用与 Tomcat 是 1:1 关系),所有在遍历 Host 下的 Container 时,如果 Container 类型是 Context ,就直接返回了。
1 2 3 4 5 6 7 8
| private Context findContext() { for (Container child : this.tomcat.getHost().findChildren()) { if (child instanceof Context) { return (Context) child; } } throw new IllegalStateException("The host does not contain a Context"); }
|
Tomcat 启动过程
在 TomcatWebServer 的 initialize 方法中会执行 tomcat 的启动。
org.apache.catalina.startup.Tomcat 的 start 方法:
1 2 3 4 5 6
| public void start() throws LifecycleException { getServer(); server.start(); }
|
初始化 Server
初始化 server 实际上就是构建一个 StandardServer 对象实例,关于 Tomcat 中的 Server 可以参考附件中的说明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public Server getServer() { if (server != null) { return server; } System.setProperty("catalina.useNaming", "false"); server = new StandardServer(); initBaseDir();
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));
server.setPort( -1 );
Service service = new StandardService(); service.setName("Tomcat"); server.addService(service); return server; }
|
小结
上面对 SpringBoot 中内嵌 Tomcat 的过程做了分析,这个过程实际上并不复杂,就是在刷新 Spring 上下文的过程中将 Tomcat 容器启动起来,并且将当前应用绑定到一个 Context ,然后添加了 Host。下图是程序的执行堆栈和执行内嵌 Tomcat 初始化和启动的时机。

下面总结下整个过程:
- 通过自定配置注册相关的 Bean ,包括一些 Factory 和 后置处理器等
- 上下文刷新阶段,执行创建 WebServer,这里需要用到前一个阶段所注册的 Bean
- 包括创建 ServletContext
- 实例化 webServer
- 创建 Tomcat 实例、创建 Connector 连接器
- 绑定 应用到 ServletContext,并添加相关的生命周期范畴内的监听器,然后将 Context 添加到 host 中
- 实例化 webServer 并且启动 Tomcat 服务
SpringBoot 的 Fatjar 方式没有提供共享 Tomcat 的实现逻辑,就是两个 FATJAT 启动可以只实例化一个 Tomcat 实例(包括 Connector 和 Host ),从前面的分析知道,每个 web 应用(一个 FATJAT 对应的应用)实例上就是映射到一个 Context ;而对于 war 方式,一个 Host 下面是可以挂载多个 Context 的。
附:Tomcat 组件说明
组件名称 |
说明 |
Server |
表示整个Servlet 容器,因此 Tomcat 运行环境中只有唯一一个 Server 实例 |
Service |
Service 表示一个或者多个 Connector 的集合,这些 Connector 共享同一个 Container 来处理其请求。在同一个 Tomcat 实例内可以包含任意多个 Service 实例,他们彼此独立。 |
Connector |
Tomcat 连接器,用于监听和转化 Socket 请求,同时将读取的 Socket 请求交由 Container 处理,支持不同协议以及不同的 I/O 方式。 |
Container |
Container 表示能够执行客户端请求并返回响应的一类对象,在 Tomcat 中存在不同级别的容器:Engine、Host、Context、Wrapper |
Engine |
Engine 表示整个 Servlet 引擎。在 Tomcat 中,Engine 为最高层级的容器对象,虽然 Engine 不是直接处理请求的容器,确是获取目标容器的入口 |
Host |
Host 作为一类容器,表示 Servlet 引擎(即Engine)中的虚拟机,与一个服务器的网络名有关,如域名等。客户端可以使用这个网络名连接服务器,这个名称必须要在 DNS 服务器上注册 |
Context |
Context 作为一类容器,用于表示 ServletContext,在 Servlet 规范中,一个 ServletContext 即表示一个独立的 web 应用 |
Wrapper |
Wrapper 作为一类容器,用于表示 Web 应用中定义的 Servlet |
Executor |
表示 Tomcat 组件间可以共享的线程池 |