点击上方关注 “终端研发部

设为“星标”,和你一起掌握更多数据库知识
作者: 枕边书
来源: zhenbianshu.github.io

Part1前言

最近对 Spring 越来越感兴趣,却在阅读它的源码时很容易被类之间的跳转和方法的嵌套绕晕,为了避免无尽的烦恼,我决定跟它做一个了断,不再追求细节,了解其启动过程和重要组件即可,之后遇到细节问题再看对应模块的源码。

我们都知道,一个 Java Web 服务进程,Web 服务器是其必不可少的组件之一,仅有 Spring 是无法受理系统的 HTTP 请求的。而且在 Java 的 Servlet 模型里,Spring 是作为 Web 服务器里的一个 Servlet 存在的,这就更能说明 Spring 和 Web 服务器的关系之亲密了。

但每个 Java 进程都只有一个主类,进程从其静态的 main 函数里启动,那么在 Spring 和 Web 服务器配合的场景中,Spring 和 Web 服务器之间是谁先启动的呢?Spring 容器和 Servlet 容器之间有包含关系吗?

要回答这两个问题,还需要分场景来看。

由于 Java Web 服务器里我们最熟悉 Tomcat,后面就以 Tomcat 来代表 Web 服务器了,而 Spring 相关我们都基于 SpringBoot 来说。

Part2Jar 包方式

实例

我们最常看到的是下面例子中的写法。

@SpringBootApplication
public class ServerStarter {public static void main(String[] args) {SpringApplication.run(ServerStarter.class, args);}
}

这种方式非常符合我们对 Java 进程启动的认知。可以看到,我们在 ServerStarter.main 方法里直接使用了 SpringApplication.run() 方法,而 Spring 启动的流程都在 SpringApplication 类内,我们后续再详细分析。

在 IDE 里我们可以直接执行这个主类来启动 Spring 应用,而脱离了 IDE,我们就只能把整个应用打成一个 fat jar,并且在 jar 包的 MANIFEST 文件内设置 Main-Class 属性值为 package.path.ServerStarter 来声明主类。这样,在使用 java -jar spring_starter.jar 时, JVM 就知道该从哪个类开始加载。

联系

Spring 容器是启动成功后, Tomcat Web 服务器的启动就要靠 Spring 了,我查看了 SpringApplication 类的代码整理出以下流程。

  1. 创建 SpringApplication 时使用在 deduceWebApplicationType() 通过一些标志类(如 org.springframework.web.servlet.DispatcherServlet) 是否存在,推断是出应用类型,一般是 WebApplicationType.SERVLET

  2. 创建 createApplicationContext() 方法内,根据应用类型创建出一个 AnnotationConfigServletWebServerApplicationContext 类型的 ConfigurableApplicationContext。

  3. 由于 AnnotationConfigServletWebServerApplicationContext 类是 ServletWebServerApplicationContext 的子类,在 ApplicationContext.refresh() 时,会通过 ServletWebServerApplicationContext.onRefresh() 方法创建一个 webServer。

  4. DispatcherServlet 通过 ServletContextInitializer -> ServletRegistrationBean<DispatcherServlet> 注册到 Tomcat 容器内,完成两者的关联。

Part3War 包方式

实例

在生产环境部署服务时,我们更多地使用 war 包的方式,因为它可以很方便地支持 jsp,而通过 jsp 我们可以给生产环境调试添加一定的灵活性。在使用 docker 部署时,也能将 Tomcat 和服务分层,便于镜像的维护。

我们知道,一个 war 包是没有自运行能力的,必须要先启动一个 Tomcat 进程,由 Tomcat 自动解压并加载 webapps 目录下的 war 包来启动服务,所以通过 war 包启动的 Spring Web 主类一定是 Tomcat 类。

在主类已存在的情况下,我们的 Spring 入口类就不再需要 main 方法了,常见写法如下:

@SpringBootApplication
public class ServerStarter extends SpringBootServletInitializer {@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder application) {return application.sources(ServerStarter.class);}
}

可以看到,Spring 入口类继承了 SpringBootServletInitializer 类,最上层的接口是 org.springframework.web;.WebApplicationInitializer

联系

查看 Tomcat 的 web.xml 配置,没有一丝 spring 的痕迹,要知道 Tomcat 是如何联系上 Spring 的,还要从 WebApplicationInitializer 接口开始。

这种实现方式需要两种新特性的支持:

  • Java 1.6 之后新添加了一个类 java.util.ServiceLoader,提供了一种新的有别于 ClassLoader 的类发现机制。当我们在 META-INF/services 文件夹内添加文件,文件名是全路径的接口,文件内容每一行是全路径的接口实现,就可以通过 ServiceLoader.load(Class interface) 方法加载到对应的接口实现,这种机制就是 SPI (Service Provider Interface),使用这种机制,加载用户实现的特定接口时就不需要类加载器扫描所有的类文件了。不过如果要加载的接口是 Java 底层类时,类加载器会是 BootstrapClassLoader 或 ExtClassLoader,加载子类时需要指定使用 classLoader 为 currentThreadClassLoader (一般是加载应用的 AppClassLoader)。

  • Servlet 3.0+ 提供了另一种替代 web.xml 的 Servlet 配置机制 ServletContainerInitializer 接口,Tomcat 会在 Servlet 容器创建后调用 ServletContainerInitializer.onStartup() 方法,便于我们向容器内注册一些 Servlet 对象。

我一路跟随文档向上查看,总结了一下整个流程。

  1. Spring 在 web 包的 META-INF/services 内添加了文件 javax.servlet.ServletContainerInitializer,内容为 org.springframework.web.SpringServletContainerInitializer。

  2. Tomcat 容器启动后,通过 ServiceLoader 加载到 SpringServletContainerInitializer 实例。

  3. SpringServletContainerInitializer 通过 @HandlesTypes 注解获取到 WebApplicationInitializer 接口的所有实现(包括 SpringBootServletInitializer、AbstractDispatcherServletInitializer 等)。

  4. SpringBootServletInitializer.onStartup() 方法内初始化了 Spring 容器。

  5. AbstractDispatcherServletInitializer.registerDispatcherServlet() 方法内实现了 DispatcherServlet 与 Tomcat 的关联。

Part4小结

看完了两种服务打包方式下 Spring 容器被加载的过程,文章开头的两个问题应该就有迹可循了。

首先 Spring 和 Web 服务器的启动先后会根据服务打包方式有所不同,使用 jar 包时是 Spring 先启动,而使用 war 包时是 Web 服务器先启动。

而 Spring 容器与 Servlet 容器的包含关系,我理解是并不存在的,A 启动了 B,所以 A 包含 B 的理论由上文也可以看出是站不住脚的。Servlet 容器和 Spring 之间只是存储的元素不同,Servlet 容器内存放着 Servlet 实例,而 Spring 中存放着各种环境变量、Bean 对象等。而它们之前的联系就是 DispatcherServlet,它既是一个 Servlet 实例,又是一个 Bean,通过 DispatcherServlet,Tomcat 可以调用到 Spring 容器内部的对象,从线程栈上来看是 Web 服务器在下,Spring 更往上。

BAT等大厂Java面试经验总结

想获取 Java大厂面试题学习资料

扫下方二维码回复「BAT」就好了

回复 【加群】获取github掘金交流群
回复 【电子书】获取2020电子书教程
回复 【C】获取全套C语言学习知识手册
回复 【Java】获取java相关的视频教程和资料
回复 【爬虫】获取SpringCloud相关多的学习资料
回复 【Python】即可获得Python基础到进阶的学习教程
回复 【idea破解】即可获得intellij idea相关的破解教程
关注我gitHub掘金,每天发掘一篇好项目,学习技术不迷路!
如果喜欢就给个“在看”

SpringBoot项目的两种打包方式分析相关推荐

  1. springboot进阶(11):springboot项目的两种部署方式

    文章目录 前言 如何发布项目 第一种 jar方式部署(默认方式) 第二种 war包方式运行 关于文件名问题 关于springboot线上部署 前言 springboot项目开发完成后续,需要部署到服务 ...

  2. 快速搭建Springboot项目的两种方式!!

    大家好,我是雄雄,欢迎关注微信公众号[雄雄的小课堂]. 前言 Springboot的特点就是简单.快速和方便,使用idea不到一分钟就可以快速搭建springboot项目,并且,在这里,你不用写spr ...

  3. SpringBoot2.1.5 (5)---快速构建SpringBoot 项目的两种方式

    快速构建SpringBoot项目的两种方发 一. 通过 http://start.spring.io/ 在线构建 二. 通过IntelliJ IDEA 快速构建 通过 http://start.spr ...

  4. React基础学习笔记(一)-react前端项目的两种搭建方式

    1.运行环境准备 需要先进行react项目的运行环境nodeJS的安装,具体安装流程可以参考: windows版本的安装配置帮助文档:https://www.cnblogs.com/liuqiyun/ ...

  5. IDEA生成springboot项目的两种方式

    文章目录 1.第一种 1.1 file-->new-->project 1.2 选择spring Initializr--> next 1.3 填写项目名等 type详解 1.4 选 ...

  6. SpringBoot项目的几种创建方式,启动、和访问

    最常用的4种方式,但除了这些以外,还有其他方式: ①在线创建 ②STS构建 ③Intell  Idea内置构建工具 ④Maven创建 STS官网:https://start.spring.io  .S ...

  7. ArcGIS MPK的两种打包方式

    最近发现用ArcGIS打包的MPK文件,如果数据的来源不一样:从shapefile源数据或者是从SDE源数据进行打包,能够引起打包文件的大小不一样已经在代码中引用文件的效率不一样的差异.开始发现这种差 ...

  8. ArcGIS MPK的两种打包方式_陈焜浩Arain_新浪博客

    最近发现用ArcGIS打包的MPK文件,如果数据的来源不一样:从shapefile源数据或者是从SDE源数据进行打包,能够引起打包文件的大小不一样已经在代码中引用文件的效率不一样的差异.开始发现这种差 ...

  9. 创建SpringBoot项目的两种姿势

    2019独角兽企业重金招聘Python工程师标准>>> Spring应该是java项目用得最多的框架之一,Spring Boot的出现更是大简化了项目开发配置,提高了开发效率,那创建 ...

最新文章

  1. 【Python培训基础】一篇文件教你py文件打包成exe
  2. ⒈Altiris cms 7.0 安装前准备工作
  3. 鸿蒙系统平板界面,鸿蒙OS平板UI界面曝光:双Dock栏设计+超级终端互联功能
  4. win 修改jvm时间_Windows 下修改Tomcat jvm参数
  5. 一年的天数 Exercise06_16
  6. testng接口自动化测试_Java+Maven+TestNG接口(API)自动化测试教程(10) 使用 Jenkins 构建自动化测试持续集成...
  7. gradle 编译java_Java的Gradle依赖关系,使用编译还是实现?
  8. PHP Cookie和Session
  9. html ios导航栏下拉菜单,Flutter -- iOS导航栏TabBar
  10. sts版本不同有影响吗_不同型号的手机充电器可以混用吗?会影响电池吗?
  11. saefetchurl java_新浪云sae给的图片操作类
  12. 算法进阶之Leetcode刷题记录
  13. 树莓派python安装xlwt_利用python包(xlrd和xlwt)处理excel
  14. C++访问WebService
  15. Vscode之运行更新出错
  16. 串口485接法图_rs485接口接线怎样操作?
  17. pwnable.tw-2018-starbound_writeup
  18. 分享java旷课版的SSM框架源码
  19. iOS越狱过程:越狱工具做了什么事情?( iOS系统结构、常见的二进制文件类型)
  20. 纯CSS 实现格子背景(国际象棋棋盘)

热门文章

  1. Java中Double与BigDecimal的互转,Date和LocalDateTime互转
  2. Scala知识点21---高阶方法
  3. JS正则表达式-附身份证号码验证(简易版)
  4. 学术应用使用node-http-proxy集成谷歌学术
  5. 玩客云刷ARMBIAN当服务器过程记录
  6. How to play PRA CandyBox game——GoDapp
  7. C++数字图像处理篇之图像加马赛克
  8. 找出10000以内的自然数中的所有完数,并统计找到的完数个数。所谓完数个数,指它恰好等于除它本身之外的因子之和
  9. rancher/ui 路由资源对应表
  10. 次世代游戏建模制作工作流程 1