百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Spring Boot源码分析——支持外部 Tomcat 容器的实现详解

toyiye 2024-06-21 12:32 11 浏览 0 评论

概述

我们知道 Spring Boot 应用能够被打成 war 包,放入外部 Tomcat 容器中运行。你是否知道 Spring Boot 是如何整合 Spring MVC 的呢?

如何使用

在我们的 Spring Boot 项目中通常会引入 spring-boot-starter-web 这个依赖,该模块提供全栈的 WEB 开发特性,包括 Spring MVC 依赖和 Tomcat 容器,我们将内部 Tomcat 的 Starter 模块排除掉,如下:

然后启动类这样写:

这样你打成 war 包就可以放入外部的 Servlet 容器中运行了。

实现原理

原理在分析 Spring MVC 源码的时候讲过,参考我的 《精尽Spring MVC源码分析 - 寻找遗失的 web.xml》 这篇文章

借助于 Servlet 3.0 的一个新特性,新增的一个 javax.servlet.ServletContainerInitializer 接口,在 Servlet 容器启动时会通过 Java 的 SPI 机制从 META-INF/services/javax.servlet.ServletContainerInitializer 文件中找到这个接口的实现类,然后调用它的 onStartup(..) 方法。

在 Spring 的 spring-web 模块中该文件是这么配置的:


一起来看看这个类:

通过 @HandlesTypes 注解指定只处理 WebApplicationInitializer 类型的类

这个过程很简单,实例化所有 WebApplicationInitializer 类型的对象,然后依次调用它们的 onStartup(ServletContext) 方法

通过打断点你会发现,有一个 DemoApplication 就是我们的启动类

这也就是为什么如果你的 Spring Boot 应用需要打成 war 包放入外部 Tomcat 容器运行的时候,你的启动类需要继承 SpringBootServletInitializer 这个抽象类,因为这个抽象类实现类 WebApplicationInitializer 接口,我们只需要继承它即可

SpringBootServletInitializer

org.springframework.boot.web.servlet.support.SpringBootServletInitializer 抽象类,实现了 WebApplicationInitializer 接口,目的就是支持你将 Spring Boot 应用打包成 war 包放入外部的 Servlet 容器中运行


在 onStartup(ServletContext) 方法中就两步:

  1. 调用 createRootApplicationContext(ServletContext) 方法,创建一个 WebApplicationContext 作为 Root Spring 应用上下文
  2. 添加一个 ContextLoaderListener 监听器,会监听到 ServletContext 的启动事件,因为 Spring 应用上下文在上面第 1 步已经准备好了,所以这里什么都不用做

第 1 步是不是和 Spring MVC 类似,同样创建一个 Root WebApplicationContext 作为 Spring 应用上下文的父对象

createRootApplicationContext 方法

createRootApplicationContext(ServletContext) 方法,创建一个 Root WebApplicationContext 对象,如下:


过程如下:

  1. 创建一个 SpringApplication 构造器,目的就是启动 Spring 应用咯


  1. 设置 mainApplicationClass,也就是你的启动类,主要用于打印日志
  2. 从 ServletContext 上下文中获取最顶部的 Root ApplicationContext 应用上下文 parent,通常这里没有父对象,所以为空
  3. 如果 parent 不为空,则先 ServletContext 中的该属性置空,因为这里会创建一个 ApplicationContext 作为 Root添加一个 ApplicationContextInitializer 初始器,用于设置现在要创建的 Root ApplicationContext 应用上下文的父容器为 parent
  4. 添加一个 ApplicationContextInitializer 初始器,目的是往 ServletContext 上下文中设置 Root ApplicationContext 为现在要创建的 Root ApplicationContext 应用上下文,并将这个 ServletContext 保存至 ApplicationContext 中注意,这个对象很关键,会将当前 ServletContext 上下文对象设置到 ApplicationContext 对象里面,那么后续就不会再创建 Spring Boot 内嵌的 Tomcat 了
  5. 设置要创建的 Root ApplicationContext 应用上下文的类型(Servlet)
  6. 对 SpringApplicationBuilder 进行扩展,调用 configure(SpringApplicationBuilder) 方法,这也就是为什么我们的启动类可以重写该方法,通常不用做什么
  7. 添加一个 ApplicationListener 监听器,用于将 ServletContext 中的相关属性关联到 Environment 环境中
  8. 构建一个 SpringApplication 对象 application,用于启动 Spring 应用
  9. 如果没有设置 source 源对象,那么这里尝试设置为当前 Class 对象,需要有 @Configuration 注解
  10. 因为 SpringApplication 在创建 ApplicationContext 应用上下文的过程中需要优先注册 source 源对象,如果为空则抛出异常
  11. 添加一个错误页面 Filter 作为 sources
  12. 调用 application 的 run 方法启动整个 Spring Boot 应用

整个过程不复杂,SpringApplication 相关的内容在前面的 《SpringApplication 启动类的启动过程》文章中已经分析过,这里的关键在于第 5 步

添加的 ServletContextApplicationContextInitializer 会将当前 ServletContext 上下文对象设置到 ApplicationContext 对象里面

ServletContextApplicationContextInitializer


可以看到会将这个 ServletContext 上下文对象设置到 ApplicationContext 中

那么我们回顾到上一篇 《Spring Boot 内嵌 Tomcat 容器的实现》 文章的 1. onRefresh 方法小节调用的 createWebServer() 方法,如下:


我们看到上面第 4 步,如果从当前 Spring 应用上下文获取到了 ServletContext 对象,不会走上面的第 3 步,也就是不创建 Spring Boot 内嵌的 Tomcat

主动调用它的 getSelfInitializer() 方法来往这个 ServletContext 对象中注册各种 Servlet、Filter 和 EventListener 对象,包括 Spring MVC 中的 DispatcherServlet 对象,该方法参考上一篇 《Spring Boot 内嵌 Tomcat 容器的实现》 文章的 2. selfInitialize 方法 小节

总结

本文分析了 Spring Boot 应用被打成 war 包后是如何支持放入外部 Tomcat 容器运行的,原理也比较简单,借助 Spring MVC 中的 SpringServletContainerInitializer 这个类,它实现了 Servlet 3.0 新增的 javax.servlet.ServletContainerInitializer 接口

  1. 通过 Java 的 SPI 机制,在 META-INF/services/javax.servlet.ServletContainerInitializer 文件中写入 SpringServletContainerInitializer 这个类,那么在 Servlet 容器启动的时候会调用这个类的 onStartup(..) 方法,会找到 WebApplicationInitializer 类型的对象,并调用他们的 onStartup(ServletContext) 方法
  2. 在我们的 Spring Boot 应用中,如果需要打成 war 包放入外部 Tomcat 容器运行,启动类则需要继承 SpringBootServletInitializer 抽象类,它实现了 WebApplicationInitializer 接口
  3. 在 SpringBootServletInitializer 中会创建一个 WebApplicationContext 作为 Root Spring 应用上下文,同时会将 ServletContext 对象设置到 Spring 应用上下文中
  4. 这样一来,因为已经存在 ServletContext 对象,那么不会再创建 Spring Boot 内嵌的 Tomcat 容器,而是对 ServletContext 进行一些初始化工作

好了,到这里关于 Spring Boot 启动 Spring 应用的整个主流程,包括内嵌 Tomcat 容器的实现,以及支持运行在外部 Servlet 容器的实现都分析完了

那么接下来,我们一起来看看 @SpringBootApplication 这个注解,也就是 @EnableAutoConfiguration 自动配置注解的实现原理

如果文章对你有帮助的话,就点赞支持一波吧,非常感谢!

如何获取Java学习资料?

转发分享此文,后台私信小编:“ 666 ”即可获取。(注:转发分享,感谢大家)

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码