热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

更多|上文_SpringBoot项目的两种打包方式分析

篇首语:本文由编程笔记#小编为大家整理,主要介绍了SpringBoot项目的两种打包方式分析相关的知识,希望对你有一定的参考价值。点击上方关注“终端研发

篇首语:本文由编程笔记#小编为大家整理,主要介绍了SpringBoot项目的两种打包方式分析相关的知识,希望对你有一定的参考价值。


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



设为“星标”,和你一起掌握更多数据库知识

作者: 枕边书
来源: 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 注册到 Tomcat 容器内,完成两者的关联。


Part3War 包方式

实例


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


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


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


@SpringBootApplication
public class ServerStarter extends SpringBootServletInitializer 
    @Override
    protected 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掘金,每天发掘一篇好项目,学习技术不迷路!

如果喜欢就给个“在看”

推荐阅读
  • Explore how Matterverse is redefining the metaverse experience, creating immersive and meaningful virtual environments that foster genuine connections and economic opportunities. ... [详细]
  • 本文介绍了如何使用 Spring Boot DevTools 实现应用程序在开发过程中自动重启。这一特性显著提高了开发效率,特别是在集成开发环境(IDE)中工作时,能够提供快速的反馈循环。默认情况下,DevTools 会监控类路径上的文件变化,并根据需要触发应用重启。 ... [详细]
  • 本文详细介绍了 Dockerfile 的编写方法及其在网络配置中的应用,涵盖基础指令、镜像构建与发布流程,并深入探讨了 Docker 的默认网络、容器互联及自定义网络的实现。 ... [详细]
  • 使用 Azure Service Principal 和 Microsoft Graph API 获取 AAD 用户列表
    本文介绍了一段通用代码示例,该代码不仅能够操作 Azure Active Directory (AAD),还可以通过 Azure Service Principal 的授权访问和管理 Azure 订阅资源。Azure 的架构可以分为两个层级:AAD 和 Subscription。 ... [详细]
  • DNN Community 和 Professional 版本的主要差异
    本文详细解析了 DotNetNuke (DNN) 的两种主要版本:Community 和 Professional。通过对比两者的功能和附加组件,帮助用户选择最适合其需求的版本。 ... [详细]
  • 本文详细介绍了如何构建一个高效的UI管理系统,集中处理UI页面的打开、关闭、层级管理和页面跳转等问题。通过UIManager统一管理外部切换逻辑,实现功能逻辑分散化和代码复用,支持多人协作开发。 ... [详细]
  • ImmutableX Poised to Pioneer Web3 Gaming Revolution
    ImmutableX is set to spearhead the evolution of Web3 gaming, with its innovative technologies and strategic partnerships driving significant advancements in the industry. ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 深入了解 Windows 窗体中的 SplitContainer 控件
    SplitContainer 控件是 Windows 窗体中的一种复合控件,由两个可调整大小的面板和一个可移动的拆分条组成。本文将详细介绍其功能、属性以及如何通过编程方式创建复杂的用户界面。 ... [详细]
  • 本文介绍如何使用Objective-C结合dispatch库进行并发编程,以提高素数计数任务的效率。通过对比纯C代码与引入并发机制后的代码,展示dispatch库的强大功能。 ... [详细]
  • Java 中 Writer flush()方法,示例 ... [详细]
  • 本章将深入探讨移动 UI 设计的核心原则,帮助开发者构建简洁、高效且用户友好的界面。通过学习设计规则和用户体验优化技巧,您将能够创建出既美观又实用的移动应用。 ... [详细]
  • ASP.NET MVC中Area机制的实现与优化
    本文探讨了在ASP.NET MVC框架中,如何通过Area机制有效地组织和管理大规模应用程序的不同功能模块。通过合理的文件夹结构和命名规则,开发人员可以更高效地管理和扩展项目。 ... [详细]
  • 本文探讨了领域驱动设计(DDD)的核心概念、应用场景及其实现方式,详细介绍了其在企业级软件开发中的优势和挑战。通过对比事务脚本与领域模型,展示了DDD如何提升系统的可维护性和扩展性。 ... [详细]
  • 本文探讨了 Spring Boot 应用程序在不同配置下支持的最大并发连接数,重点分析了内置服务器(如 Tomcat、Jetty 和 Undertow)的默认设置及其对性能的影响。 ... [详细]
author-avatar
DilWilling
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有