Spring Boot 嵌入式Web容器
前言
最近在学习Spring Boot相关的课程,过程中以笔记的形式记录下来,方便以后回忆,同时也在这里和大家探讨探讨,文章中有漏的或者有补充的、错误的都希望大家能够及时提出来,本人在此先谢谢了!
开始之前呢,希望大家带着几个问题去学习:
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.0 | 9.X | 9.X | 2.X |
3.1 | 8.X | 8.X | 1.X |
3.0 | 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>
前三者是 Servlet Web
实现,最后则是 Reactive Web
的实现。值得注意的是,当我们引用的是 Servlet Web
功能模块时,它会自动集成 Tomcat
,里面包含了 Tomcat
的 Maven
依赖包,也就是说 Tomcat
是默认的 Servlet Web
容器。Servlet Web
模块的 Maven
依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
而如果引用的是 Reactive Web
功能模块时,则会默认集成 netty Web Server
。Reactive Web
模块的依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
不过,上面的三种 Servlet Web
容器也能作为 Reactive Web
容器 ,并允许替换默认实现 Netty Web Server
,因为 Servlet
3.1+容器同样满足 Reactive
异步非阻塞特性。
接下来,我们重点讨论嵌入式 Web 容器的启动流程。
注:本篇文章所用到的
Spring Boot
版本是2.1.6.BUILD-SNAPSHOT
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);
}
}
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;
}
}
以上,在 SpringApplication
的构造阶段确定了当前应用的类型,该类型名称存储在 webApplicationType
字段中。
2.2、容器启动流程
接着进入容器启动流程,进入重载的 run 方法中:
public class SpringApplication {
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
...
private WebApplicationType webApplicationType;
...
public ConfigurableApplicationContext run(String... args) {
...
ConfigurableApplicationContext context = null;
try {
...
// 1、通过 createApplicationContext 方法创建对应的 ApplicationContext 应用上下文,进入 1.1 查看具体实现
context = createApplicationContext();
...
// 2、该方法实质是启动 Spring 应用上下文的,但 Spring Boot 嵌入式容器也在该过程中被启动,入参是上下文对象,我们进入 2.1 进行跟踪
refreshContext(context);
...
}
...
}
// 1.1、
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
// 这里就是通过 webApplicationType 属性,判断应用类型,来创建不同的 ApplicationContext 应用上下文
switch (this.webApplicationType) {
case SERVLET:
// 返回的是 Servlet Web ,具体对象为 AnnotationConfigServletWebServerApplicationContext,
// 该类有一个关键父类 ServletWebServerApplicationContext
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
// 返回的是 Reactive Web,具体对象为 AnnotationConfigReactiveWebServerApplicationContext
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
// 应用类型是非 Web 时,返回 AnnotationConfigApplicationContext
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
...
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
// 2.1
private void refreshContext(ConfigurableApplicationContext context) {
// 里面调用的是 refresh 方法,进入 2.2 继续跟踪
refresh(context);
...
}
// 2.2
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
// 最终调用了 所有应用上下文的统一抽象类 AbstractApplicationContext 中的 refresh 方法,进入 3 查看实现
((AbstractApplicationContext) applicationContext).refresh();
}
...
}
AbstractApplicationContext
是 Spring
应用上下文的核心启动类,Spring
的 ioc 从这里就开始进入创建流程。在后续在 Spring
系列的文章中会进行详细讨论,我们这里只关注容器创建的部分。
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
...
// 3
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
try {
...
// Web 容器在这个方法中启动,但在当前抽象类中这个方法是个空实现,具体由 ApplicationContext 上下文的子类对象进行重写,
// 我们进入 4 查看其中一个子类的实现
onRefresh();
...
}
}
...
}
...
}
这里以 Servlet web
为例,具体上下文对象是 AnnotationConfigServletWebServerApplicationContext
,该类实现了 ServletWebServerApplicationContext
类,onRefresh 方法由该类进行重写。
public class ServletWebServerApplicationContext extends GenericWebApplicationContext
implements ConfigurableWebServerApplicationContext {
...
// 4
@Override
protected void onRefresh() {
...
try {
// 里面调用了 createWebServer 方法,进入 5 查看实现
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
...
// 5
private void createWebServer() {
// WebServer 就是容器对象,是一个接口,其对应的实现类分别是:
// JettyWebServer、TomcatWebServer、UndertowWebServer、NettyWebServer。这些容器对象由对应的容器工厂类进行创建
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
// 当 webServer 等于 null ,也就是容器还没创建时,进入该 if 中
if (webServer == null && servletContext == null) {
// 这里是获取 创建 Servlet Web 容器的工厂类,也是一个接口,有三个实现,分别是:
// JettyServletWebServerFactory、TomcatServletWebServerFactory、UndertowServletWebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
// 通过容器工厂类的 getWebServer 方法,创建容器对象。这里以创建 Tomcat 为例,来继续跟踪容器的创建流程,
// 跳到 6 查看 TomcatServletWebServerFactory 的 getWebServer 方法
this.webServer = factory.getWebServer(getSelfInitializer());
}
...
}
}
TomcatServletWebServerFactory
是创建 Tomcat
的 Web 容器工厂类,但这个工厂类是如何被创建的呢?这里将会在文章的第三部分进行详细讨论。
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {
...
// 6、
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
// 方法中先进行创建 Tomcat 的流程,如 Container 、Engine、Host、Servlet 几个容器的组装。
// 后续有机会再对 Tomcat 进行详细讨论,这里就不深入了
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);
// 通过 getTomcatWebServer 方法返回 TomcatWebServer 容器对象。进入 7 查看接下来的流程
return getTomcatWebServer(tomcat);
}
...
// 7、这里通过 TomcatWebServer 的构造方法创建该对象。进入 8 继续跟踪
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}
...
}
TomcatWebServer
是具体的容器对象,在其对应的工厂类中进行创建,其实现了 WebServer
接口,并在该对象中进行 Tomcat
的启动流程。
public class TomcatWebServer implements WebServer {
...
// 8、
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
// 进入 initialize 方法中,查看实现
initialize();
}
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
...
// 调用 Tomcat 的 start 方法,正式启动
this.tomcat.start();
...
// 启动守护线程来监听http请求
startDaemonAwaitThread();
}
...
}
}
...
}
到这里,Web 容器的启动流程就结束了,以上是以 Servlet Web
及其具体的 Tomcat
容器为例子进行的讨论,这也是大部分开发者常用的体系。接着来对整个过程做一个总结。
- 首先通过引入的 Web 模块 Maven 依赖 ,来判断当前应用的类型,如 Servlet Web 或 Reactive Web。并根据应用类型来创建相应的 ApplicationContext 上下文对象。
- 然后调用了 ApplicationContext 的父抽象类 AbstractApplicationContext 中的 refresh 方法,又在该方法中调用了子类的 onRefresh 方法。
- 最后是在 onRefresh 中通过容器的工厂类创建具体容器对象,并在该容器对象中进行启动。
3、加载 Web 容器工厂
上面说过, Web 容器对象是由其对应的工厂类进行创建的,那容器工厂类又是怎么创建呢?我们这里就来看一看。在《Spring Boot 自动装配(二)》的 1.2 小节说过, Spring Boot
启动时,会读取所有 jar 包中 META-INF
文件夹下的 spring.factories
文件,并加载文件中定义好的类,如:
...
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
...
其中有一个 ServletWebServerFactoryAutoConfiguration
类:
@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 {
...
}
该类通过 @Import
导入了 ServletWebServerFactoryConfiguration
中的三个内部类,这三个内部类就是用来创建容器工厂,我们进入其中查看具体实现:
@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 {
@Bean
public 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 {
@Bean
public 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 {
@Bean
public UndertowServletWebServerFactory undertowServletWebServerFactory() {
return new UndertowServletWebServerFactory();
}
}
可以看到,主要是通过引入相应 Web 容器的 Maven
依赖,来判断容器对应的 Class
是否存在,存在则创建相应的容器工厂类。
4、总结
最后,来对 Spring Boot
嵌入式 Web 容器做一个整体的总结。Spring Boot
支持的两大 Web 容器体系,一个是 Servlet Web
,另一个是 Reactive Web
,它们都有其具体的容器实现,相信大多数开发者使用的都是前者,且最常用的容器实现也是 Tomcat
,所以这篇文章主要讨论的也是 Spring Boot
启动 Tomcat
嵌入式容器的流程。
以上就是本章的内容,如果文章中有错误或者需要补充的请及时提出,本人感激不尽。
Spring Boot 嵌入式Web容器的更多相关文章
- 【串线篇】spring boot嵌入式Servlet容器自动配置原理
EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自动配置? @AutoConfigureOrder(Ordered.HIGHEST_PREC ...
- 【串线篇】spring boot嵌入式Servlet容器启动原理;
什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat: 获取嵌入式的Servlet容器工厂: 1).SpringBoot应用启动运行run方法 2).r ...
- Spring Boot嵌入式的Servlet容器
一.查看SpringBoot默认的嵌入式Servlet容器(默认使用的是tomcat) 在IDEA的项目的pom文件中按Ctrl + shift + Alt + U可以打开SpringBoot依赖的图 ...
- 通过docker-composer启动容器nginx,并完成spring.boot的web站点端口转发
前面已经讲过2篇基于docker的mysql.redis容器编排并启动.这次将练习下nginx的docker方式的部署,以及通过nginx去代理宿主主机上的Web服务应该怎么配 PS:(这里由于ngi ...
- Jetty嵌入式Web容器攻略
Jetty是一个用 Java 实现.开源.基于标准的,并且具有丰富功能的 Http 服务器和 Web 容器.Jetty中应用最广泛的一项功能就是可以作为嵌入式Web容器. 在开发阶段,可以使用Jett ...
- 【spring boot】5.spring boot 创建web项目并使用jsp作前台页面
贼烦的是,使用spring boot 创建web项目,然后我再idea下创建的,but 仅仅启动spring boot的启动类,就算整个项目都是好着的,就算是能够进入controller中,也不能成功 ...
- Spring Boot 嵌入式 Tomcat 文件上传、url 映射虚拟路径
1.Java web 应用开发完成后如果是导入外置的 Tomcat 的 webapps 目录的话,那么上传的文件可以直接的放在应用的 web 目录下去就好了,浏览器可以很方便的进行访问. 2.Spri ...
- Spring Boot开发Web应用之Thymeleaf篇
前言 Web开发是我们平时开发中至关重要的,这里就来介绍一下Spring Boot对Web开发的支持. 正文 Spring Boot提供了spring-boot-starter-web为Web开发予以 ...
- Spring Boot 容器选择 Undertow 而不是 Tomcat Spring Boot 内嵌容器Unde
Spring Boot 内嵌容器Undertow参数设置 配置项: # 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程 # 不要设置过大,如果过大,启动 ...
随机推荐
- jq 添加内容
向页面动态添加内容,一般用于动态网页,需要即时请求数据,并更新在页面上,使用append()更多一些,empty() - 清空所有子元素,remove() - 清除自身所有子元素. append() ...
- 【错误收集】SVN冲突解决 标签: 错误收集 2016-03-13 08:44 624人阅读 评论(24) 收藏
最近在倒代码,这真的是一件挺低效率的事情的,但是为了之后工作的进行,必须把这些已经做好的界面,做好的功能搬到新的框架上来,所以安排了10来个同学一起倒代码,因为大家共用一个解决方案,所以使用svn来进 ...
- CPU核数和线程数查找
方法1: 方法2:
- linux下arm平台Qt编译环境搭建与解析
一.概述: 我们知道QTcreator.这仅仅是个IDE,他包含了一个编译器--qmake.这两者的关系与codeblocks和g++的关系一样,首先要明确这些. 而我们在linu ...
- Quick BI 3.0 - 强大的多维分析表格:交叉表
写在开头 对于普通的表格展示数据,相信大家都非常熟悉了,今天给大家介绍的是BI领域的分析利器-交叉表,这个在BI分析场景中使用占比最多的分析利器.通过交叉表对数据的承载和管理,用户可以一目了然地分析出 ...
- 《C语言深度解剖》学习笔记之指针和数组
第4章 指针和数组 1. int *p=NULL 和 *p=NULL 有什么区别 int *p = NULL; 第一句代码的意思是:定义一个指针变量p,其指向的内存里面保存的是 int类型的数据:在定 ...
- MySQL列出当前月的每一天
因为工作的原因,要用MySQL列出当前月份每一天的日期,自己查了下网上资料都是列出最近一个月的日期的解决方案,自己根据查到的的方案,修改成了下面两个方案,在此记录下: 方案一: SELECT date ...
- Struts2整合Spring方法及原理
一. Struts 2框架整合Spring步骤 1. 复制文件.复制struts2-spring-plugin-x-x-x.jar和spring.jar到WEB-INF/lib目录下.其中的x对应 ...
- Mac 安装homebrew,pkgutil --pkgs列出安装包
Mac 安装homebrew Homebrew官网 http://brew.sh/index_zh-cn.html Homebrew是神马 Linux系统有个让人蛋疼的通病,软件包依赖,好在当前主流的 ...
- OpenStack项目及组件功能简单介绍
核心项目3个 1.控制台 服务名:Dashboard 项目名:Horizon 功能:web方式管理云平台,建云主机,分配网络,配安全组,加云盘 2.计算 服务名:计算 项目名:Nova 功能:负责响应 ...