Spring Boot 支持的两大 Web 容器体系,一个是 Servlet Web ,另一个是 Reactive Web ,它们都有其具体的容器实现,相信大多数开发者使用的都是前者,且最常用的容器实现也是 Tomcat,所以这篇文章主要讨论的也是 Spring Boot 启动 Tomcat 嵌入式容器的流程。
前言
开始之前呢,我们带着几个问题去学习:
1、Spring Boot 嵌入式Web容器是什么?
2、整体流程或结构是怎样的?
3、核心部分是什么?
4、怎么实现的?
1、起源
在当今的互联网场景中,与终端用户交互的应用大多数是 Web 应用,其中 Java Web 应用尤为突出,其对应的 Java Web 容器发展至今也分为 Servlet Web 容器和 Reactive Web 容器,前者的使用率大概占比是百分之九十左右,其具体的实现有 Tomcat、Jetty 和 Undertow;而后者出现较晚,且技术栈体系并未完全成熟,还有待时间验证可行性,它的默认实现为 Netty Web Server。其中的 Servlet 规范与三种 Servlet 容器的版本关系如下:
Servlet 规范
Tomcat
Jetty
Undertow
4
9.X
9.X
2.X
3.1
8.X
8.X
1.X
3
7.X
8.X
N/A
2.5
6.X
8.X
N/A
以上 Web 容器均被 Spring Boot 嵌入至其中作为其核心特性,来简化 Spring Boot 应用启动流程。Spring Boot 通过 Maven 依赖来切换应用的嵌入式容器类型,其对应的 Maven jar 分别是:
<!-- Tomcat --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></dependency><!-- undertow --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-undertow</artifactId></dependency><!-- jetty --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jetty</artifactId></dependency><!-- netty Web Server --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-reactor-netty</artifactId></dependency>
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
前三者是 Servlet Web 实现,最后则是 Reactive Web 的实现。值得注意的是,当我们引用的是 Servlet Web 功能模块时,它会自动集成 Tomcat ,里面包含了 Tomcat 的 Maven 依赖包,也就是说 Tomcat 是默认的 Servlet Web 容器。Servlet Web 模块的 Maven 依赖如下:
不过,上面的三种 Servlet Web 容器也能作为 Reactive Web 容器 ,并允许替换默认实现 Netty Web Server,因为 Servlet 3.1+容器同样满足 Reactive 异步非阻塞特性。
接下来,我们重点讨论嵌入式 Web 容器的启动流程。
注:本篇文章所用到的 Spring Boot版本是 2.3.3.RELEASE
2、容器启动流程解析
Spring Boot 嵌入式容器启动时会先判断当前的应用类型,是 Servlet Web 还是 Reactive Web ,之后会创建相应类型的 ApplicationContext 上下文,在该上下文中先获取容器的工厂类,然后利用该工厂类创建具体的容器。接下来,我们进行详细讨论。
从 Spring Boot 启动类开始:
@SpringBootApplication
public class DiveInSpringBootApplication {
public static void main(String[] args){
SpringApplication.run(DiveInSpringBootApplication.class, args);}}
1.
2.
3.
4.
5.
6.
2.1获取应用类型
先来看看,获取应用类型的过程,进入 run 的重载方法:
public class SpringApplication {//1、该方法中,先构造 SpringApplication 对象,再调用该对象的 run 方法。我们进入第二步查看构造过程
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args){
return new SpringApplication(primarySources).run(args);}//2、webApplicationType 存储的就是应用的类型,通过 deduceFromClasspath 方法返回。// 我们进入第三步查看该方法实现public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources){
...
this.webApplicationType= WebApplicationType.deduceFromClasspath();
...
}}
public enum WebApplicationType {
private static final String[] SERVLET_INDICATOR_CLASSES ={"javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext"};
private static final String WEBMVC_INDICATOR_CLASS ="org.springframework."+"web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS ="org."+"springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS ="org.glassfish.jersey.servlet.ServletContainer";//3、这里其实是根据引入的 Web 模块 jar 包,来判断是否包含各 Web 模块的类,来返回相应的应用类型
static WebApplicationType deduceFromClasspath(){// 当 DispatcherHandler 类存在,DispatcherServlet 和 ServletContainer 不存在时,
// 返回 Reactive ,表示当前 Spring Boot 应用类型是 Reactive Web 。
// 前者是 Reactice Web jar 中的类,后两者是 Servlet Web 中的。if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS,null)&&!ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS,null)&&!ClassUtils.isPresent(JERSEY_INDICATOR_CLASS,null)){
return WebApplicationType.REACTIVE;}// 这里判断是非 Web 应用类型for (String className : SERVLET_INDICATOR_CLASSES){
if (!ClassUtils.isPresent(className,null)){
return WebApplicationType.NONE;}}// 以上都不满足时,最后返回 Servlet 。return WebApplicationType.SERVLET;}}
@Configuration
class ServletWebServerFactoryConfiguration {// 通过 @ConditionalOnClass 判断 Servlet 、Tomcat、UpgradeProtocol 这三个 Class 是否存在,
// 当引用的是 Tomcat Maven 依赖时,则 Class 才存在,并创建 Tomcat 的容器工厂类 TomcatServletWebServerFactory@Configuration@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class})
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Beanpublic TomcatServletWebServerFactory tomcatServletWebServerFactory(){
return new TomcatServletWebServerFactory();}}// 当引用的是 Jetty Maven 依赖时,@ConditionalOnClass 条件才满足,
// 创建的容器工厂类是 JettyServletWebServerFactory@Configuration@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class})
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Beanpublic JettyServletWebServerFactory JettyServletWebServerFactory(){
return new JettyServletWebServerFactory();}}// 当引用的是 Undertow Maven 依赖时,@ConditionalOnClass 条件才满足,
// 创建的容器工厂类是 UndertowServletWebServerFactory@Configuration@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class})
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {
@Beanpublic UndertowServletWebServerFactory undertowServletWebServerFactory(){
return new UndertowServletWebServerFactory();}}
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.
可以看到,主要是通过引入相应 Web 容器的 Maven 依赖,来判断容器对应的 Class 是否存在,存在则创建相应的容器工厂类。
4、总结
最后,来对 Spring Boot 嵌入式 Web 容器做一个整体的总结。Spring Boot 支持的两大 Web 容器体系,一个是 Servlet Web ,另一个是 Reactive Web ,它们都有其具体的容器实现,相信大多数开发者使用的都是前者,且最常用的容器实现也是 Tomcat,所以这篇文章主要讨论的也是 Spring Boot 启动 Tomcat 嵌入式容器的流程。