精尽Spring Boot源码分析 - 内嵌Tomcat容器的实现

Spring Boot内嵌Tomcat session超问题 最近让Spring Boot内嵌Tomcat的session超问题给坑了一把。 在应用中需要设置session超间,然后就习惯的在application.properties配置文件中设置如下, server.session.timeout=90 这里把超间设置的短些,主要想看看到底有没有起作用(不能设值30min然后再看吧,那样太不人道了)。结果没起作用,百度下发现Spr... 阅读详情

概述

我们知道 Spring Boot 能够创建独立的 Spring 应用,内部嵌入 Tomcat 容器(Jetty、Undertow),让我们的 jar 无需放入 Servlet 容器就能直接运行。那么对于 Spring Boot 内部嵌入 Tomcat 容器的实现你是否深入的学习过?或许你可以通过这篇文章了解到相关内容。

在上一篇 《SpringApplication 启动类的启动过程》 文章分析了 SpringApplication#run(String... args) 启动 Spring 应用的主要流程,不过你是不是没有看到和 Tomcat 相关的初始化工作呢?

别急,在刷新 Spring 应用上下文的过程中会调用 onRefresh() 方法,在 Spring Boot 的 ServletWebServerApplicationContext 中重写了该方法,此时会创建一个 Servlet 容器(默认为 Tomcat),并添加 IoC 容器中的 Servlet、Filter 和 EventListener 至 Servlet 上下文。

例如 Spring MVC 中的核心组件 DispatcherServlet 对象会添加至 Servlet 上下文,不熟悉 Spring MVC 的小伙伴可查看我前面的 《精尽Spring MVC源码分析 - 一个请求的旅行过程》 这篇文章。同时,在 《精尽Spring MVC源码分析 - 寻找遗失的 web.xml》 这篇文章中有提到过 Spring Boot 是如何加载 Servlet 的,感兴趣的可以先去看一看,本文会做更加详细的分析。

接下来,我们一起来看看 Spring Boot 内嵌 Tomcat 的实现。

文章的篇幅有点长,处理过程有点绕,每个小节我都是按照优先顺序来展述的,同时,主要的流程也标注了序号,请耐心查看📝

如何使用

在我们的 Spring Boot 项目中通常会引入 spring-boot-starter-web 这个依赖,该模块提供全栈的 WEB 开发特性,包括 Spring MVC 依赖和 Tomcat 容器,这样我们就可以打成 jar 包直接启动我们的应用,如下:

 

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>

如果不想使用内嵌的 Tomcat,我们可以这样做:

 

<packaging>war</packaging> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency>

然后启动类这样写:

 

// 方式三 @SpringBootApplication public class Application extends SpringBootServletInitializer { // 可不写 public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(Application.class); } }

这样你打成 war 包就可以放入外部的 Servlet 容器中运行了,具体实现查看下一篇文章,本文分析的主要是 Spring Boot 内嵌 Tomcat 的实现。

回顾

在上一篇 《SpringApplication 启动类的启动过程》 文章分析 SpringApplication#run(String... args) 启动 Spring 应用的过程中讲到,在创建好 Spring 应用上下文后,会调用其 AbstractApplication#refresh() 方法刷新上下文,该方法涉及到 Spring IoC 的所有内容,参考 《死磕Spring之IoC篇 - Spring 应用上下文 ApplicationContext》

 

/** * 刷新上下文,在哪会被调用? * 在 **Spring MVC** 中,{@link org.springframework.web.context.ContextLoader#initWebApplicationContext} 方法初始化上下文时,会调用该方法 */ @Override public void refresh() throws BeansException, IllegalStateException { // <1> 来个锁,不然 refresh() 还没结束,你又来个启动或销毁容器的操作,那不就乱套了嘛 synchronized (this.startupShutdownMonitor) { // <2> 刷新上下文环境的准备工作,记录下容器的启动时间、标记'已启动'状态、对上下文环境属性进行校验 prepareRefresh(); // <3> 创建并初始化一个 BeanFactory 对象 `beanFactory`,会加载出对应的 BeanDefinition 元信息们 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // <4> 为 `beanFactory` 进行一些准备工作,例如添加几个 BeanPostProcessor,手动注册几个特殊的 Bean prepareBeanFactory(beanFactory); try { // <5> 对 `beanFactory` 在进行一些后期的加工,交由子类进行扩展 postProcessBeanFactory(beanFactory); // <6> 执行 BeanFactoryPostProcessor 处理器,包含 BeanDefinitionRegistryPostProcessor 处理器 invokeBeanFactoryPostProcessors(beanFactory); // <7> 对 BeanPostProcessor 处理器进行初始化,并添加至 BeanFactory 中 registerBeanPostProcessors(beanFactory); // <8> 设置上下文的 MessageSource 对象 initMessageSource(); // <9> 设置上下文的 ApplicationEventMulticaster 对象,上下文事件广播器 initApplicationEventMulticaster(); // <10> 刷新上下文时再进行一些初始化工作,交由子类进行扩展 onRefresh(); // <11> 将所有 ApplicationListener 监听器添加至 `applicationEventMulticaster` 事件广播器,如果已有事件则进行广播 registerListeners(); // <12> 设置 ConversionService 类型转换器,**初始化**所有还未初始化的 Bean(不是抽象、单例模式、不是懒加载方式) finishBeanFactoryInitialization(beanFactory); // <13> 刷新上下文的最后一步工作,会发布 ContextRefreshedEvent 上下文完成刷新事件 finishRefresh(); } // <14> 如果上面过程出现 BeansException 异常 catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // <14.1> “销毁” 已注册的单例 Bean destroyBeans(); // <14.2> 设置上下文的 `active` 状态为 `false` cancelRefresh(ex); // <14.3> 抛出异常 throw ex; } // <15> `finally` 代码块 finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... // 清除相关缓存,例如通过反射机制缓存的 Method 和 Field 对象,缓存的注解元数据,缓存的泛型类型对象,缓存的类加载器 resetCommonCaches(); } } }

在该方法的第 10 步可以看到会调用 onRefresh() 方法再进行一些初始化工作,这个方法交由子类进行扩展,那么在 Spring Boot 中的 ServletWebServerApplicationContext 重写了该方法,会创建一个 Servlet 容器(默认为 Tomcat),也就是当前 Spring Boot 应用所运行的 Web 环境。

第 13 步会调用 onRefresh() 方法,ServletWebServerApplicationContext 重写了该方法,启动 WebServer,对 Servlet 进行加载并初始化

类图

由于整个 ApplicationContext 体系比较庞大,下面列出了部分类

DispatcherServlet 自动配置类

在开始之前,我们先来看看 org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration 这个自动配置类,部分如下:

 

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) // 最高优先级的自动配置 @Configuration(proxyBeanMethods = false) // 作为一个配置类,不进行 CGLIB 提升 @ConditionalOnWebApplication(type = Type.SERVLET) // Servlet 应用的类型才注入当前 Bean @ConditionalOnClass(DispatcherServlet.class) // 存在 DispatcherServlet 这个类才注入当前 Bean @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class) // 在 ServletWebServerFactoryAutoConfiguration 后面进行自动配置 public class DispatcherServletAutoConfiguration { public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet"; public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration"; // 作为一个配置类,不进行 CGLIB 提升 @Configuration(proxyBeanMethods = false) // 满足条件则注入当前 DispatcherServlet(需要 Spring 上下文中不存在) @Conditional(DefaultDispatcherServletCondition.class) // 存在 ServletRegistration 这个类才注入当前 Bean @ConditionalOnClass(ServletRegistration.class) // 注入两个配置对象 @EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class }) protected static class DispatcherServletConfiguration { // 定义一个 DispatcherServlet 的 Bean @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; } } // 作为一个配置类,不进行 CGLIB 提升 @Configuration(proxyBeanMethods = false) // 满足条件则注入当前 DispatcherServletRegistrationBean @Conditional(DispatcherServletRegistrationCondition.class) // 存在 ServletRegistration 这个类才注入当前 Bean @ConditionalOnClass(ServletRegistration.class) // 注入一个配置对象 @EnableConfigurationProperties(WebMvcProperties.class) // 先注入上面的 DispatcherServletConfiguration 对象 @Import(DispatcherServletConfiguration.class) protected static class DispatcherServletRegistrationConfiguration { // 为 DispatcherServlet 定义一个 RegistrationBean 对象,目的是往 ServletContext 上下文中添加 DispatcherServlet @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) // 需要存在名称为 `dispatcherServlet` 类型为 DispatcherServlet 的 Bean @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) { DispatcherServletRegi

Spring Boot 2 实战:使用 Undertow 来替代Tomcat 作为Web 容器 1. Undertow 简介 Undertow 是一个采用 Java 开发的灵活的高性能 Web 服务器,提供包括阻塞基于 NIO 的非堵塞机制。Undertow 是红帽公司的开源产品,是 Wildfly 默认的 Web 服务器。Undertow 提供一个基础的架构用来构建 Web 服务器,这是一个完全为嵌入式设计的项目,提供易用的构建器 API,完全向下兼容 Java EE Servlet 3.1 低级非堵塞的处理器。 2. Undertow特点 高性能 在多款同类产品的压测中,在高并发情况下表现. 阅读详情

相关推荐

Spring Boot框架知识总结(超详细)

IOC容器填加相应依赖配置可以实现在自定义配置properties配置提示@ConfigurationProperties(prefix = “person”)注解的作用是将配置文件中以person开头的属性值通过setXX()方法注入到实体类对应属性中。@Component注解的作用是将当前注入属性值的Person类对象作为Bean组件放到Spring容器中,只有这样才能被@ConfigurationProperties注解进行赋值。

m0_74824755的博客 1633

SpringBoot内嵌Tomcat原理

一般我们启动都需要单独的去安装tomcat,而Springboot自身却直接整合了Tomcat,什么原理呢?

meser88的博客 1069

Tomcat源码分析-spring boot集成tomcat

在分析源码前,我们先来了解下 spring 的 SPI 机制。我们知道,jdk 为了方便应用程序进行扩展,提供了默认的 SPI 实现(ServiceLoader),dubbo 也有自己的 SPI。spring 也是如此,他为我们提供了,允许开发人员通过文件进行扩展,下面举一个例子方便理解假如,我想要往 spring 容器中添加一个做一些初始化工作,我们可以借助 spring 提供的这个 SPI 功能完成这个需求。首先,在项目中创建我们再写个 test case,便可以通过 SPI 的方式获取我们定义的。

qq_33291299的博客 825

自定义简易版SpringBoot实现SpringBoot MVC及内嵌Tomcat启动、DispatcherServlet注册组件扫描功能

作业要求: 自定义简易版SpringBoot实现SpringBoot MVC及内嵌Tomcat启动、DispatcherServlet注册组件扫描功能。 程序通过main方法启动,可以自动启动tomcat服务器 可以自动创建加载DispatcherServlet组件到ServletContext中 可以自动通过@ComponentScan扫描Controller等组件 Controller组件可以处理浏览器请求,返回响应结果 作业提示: 当实现了Servlet3.0规范的容器(比如Tomcat7及以上

weixin_43960071的博客 1302

SpringBoot

Springboot 1.1、使用SpringBoot的原因 1、springspringmvc框架需要大量的配置文件,还需要配置各种对象,把对象放到容器中才能使用 。需要了解其他框架的配置规则 2、springboot相当于不要配置文件的springspringmvc。常用的框架第三方库都配置好了。 3springboot开发效率高。 1.2、JavaConfig JavaConfig:使用java类作为xml文件的替代,是配置spring容器的纯java代码方式。 使用两个注解: 1、@Conf

jfsjqy的博客 594

SpringBoot学习(四):内嵌Servlet引擎(Tomcat,Jetty等)的实现原理

概述 SpringBoot使用main方法启动的一个重要特性是,不需要打包成war部署到Tomcat这种Servlet容器中,而是只需打包成jar,然后通过java或mvn等命令运行这个jar包,然后应用就可以在指定的端口监听客户端的连接请求了。 在SpringBoot内部主要是引用了embedded的Tomcat或Jetty等作为Servlet引擎,由该Servlet引擎负责接收Web请求并交...

源码之下,了无秘密 6615

Unable to create tempdir.java.io.tmpdir is set to /tmp 启动项目提示这个是什么问题

"Unable to create tempdir.java.io.tmpdir is set to /tmp" 这个错误提示表明在启动项目,无法创建目录,而。如果以上方法无法解决问题,建议查看应用程序启动日志系统日志以获取更多详细信息,以便更准确地确定问题的根本原因。:如果更改了权限或临目录位置,尝试重新启动您的应用程序服务器。确保输出中的权限设置允许您的应用程序创建文件。:确保磁盘有足够的可用空间来创建文件。替换为您想要使用的自定义临目录路径。目录作为临目录,可以考虑更改。

qq_33192454的博客 6128

玩转 Spring Boot 原理篇(内嵌Tomcat实现原理&优雅停机源码剖析)

0.0.0. 历史文章整理玩转 Spring Boot 入门篇玩转 Spring Boot 集成篇(MySQL、Druid、HikariCP)玩转 Spring Boot 集成篇(MyBatis、JPA、事务支持)玩转 Spring Boot 集成篇(Redis)玩转 Spring Boot 集成篇(Actuator、Spring Boot Admin)玩转 Sprin...

一猿小讲 658

Spring Boot框架总结(超级详细)

IOC容器填加相应依赖配置可以实现在自定义配置properties配置提示@ConfigurationProperties(prefix = "person")注解的作用是将配置文件中以person开头的属性值通过setXX()方法注入到实体类对应属性中。@Component注解的作用是将当前注入属性值的Person类对象作为Bean组件放到Spring容器中,只有这样才能被@ConfigurationProperties注解进行赋值。

tangjieqing的博客 1万+

springboot内嵌tomcat源码分析DispatchServlet装载过程-springboot(1)

基于serverlect3l 将需要扫描的接口放在META_INF里即可 @handlestypes可以将Class拿到

weixin_44110695的博客 889

Spring Boot整合Tomcat底层源码分析

在目录下,定义一个启动类,并使用java复制代码在java复制代码@Override");本文通过深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,特别是其与Tomcat的整合方式,展示了Spring Boot的强大功能灵活性。同,通过手写模拟Spring Boot的启动过程,进一步加深了对Spring Boot内部工作机制的理解。希望本文能为读者在使用Spring Boot进行开发提供有益的参考帮助。

喜欢猪猪 1379

Spring Boot内嵌servlet容器启动原理源码分析

通过@AutoConfiguration注解加载ServletWebServerFactoryAutoConfiguration配置类,该类通过@Import注解导入符合条件的内嵌servlet容器的配置(EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow),注册对应的servletwebserver的bean信息,spring boot在执行run方法过程中,会执行onRefresh方法,在该方法中会进行webServer容器创建。 @Override protec

chen495689231的专栏 277

Tomcat源码分析-spring boot集成tomcat

在此,spring boot 集成 tomcat 的代码分析便告一段落,最后,我们用一张图总结下整个逻辑(查看原图spring boot 利用 spring 的 SPI 的机制加载 EmbeddedServletContainerAutoConfiguration 该配置类,将 TomcatEmbeddedServletContainerFactory 加载到 spring 容器中。

二师兄撒欢之地 375

springboot源码分析 (一)启动流程及内嵌tomcat源码

本来想一篇分析完springboot核心源码的,发现内容有点多,就拆分为多个章节。 按照个人理解,springboot核心有这么几个(相对于spring来说): 1.tomcat是如何内嵌的 2.自动装配原理 自动装配稍微分析了一下,可能不是很详细自定义starter及自动配置源码分析 3.事件驱动机制 4.配置文件怎么被加载的 5.热部署原理 这一章作为起点,先分析一下springboot启动流程tomcat是如何被内嵌的,不对其他问题做过多分析。 springboot启动流程...

全场梦游c的博客 683
上一篇: 全网最容易理解的spring dispatcher请求流程解析,看不懂你打我
下一篇: 一文带你掌握SpringCloud高可用服务注册中心Eureka
Fightevery
博客等级 码龄5年 20粉丝 223原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值