30

Spring Boot 解析(二):FatJar 启动原理

 4 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzI4MDQwOTU5MQ%3D%3D&%3Bmid=2247483788&%3Bidx=1&%3Bsn=12dc07820a8341f76f9ccd073c32fe5f&%3Bchksm=ebb9a514dcce2c0209a6d0971037672a13bd9c651e6bf819c806173a560e59b94cf1301e35ea&%3Btoken=54667446&%3Blang=zh_CN
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Spring boot 系列,主要通过解读 Spring Boot 源码, 分析其内部实现机制。本文该系列的第二篇,主要带你解析 Spring Boot 可运行 jar 启动背后的原理。

Jnmmyav.png!web

Spring Boot 应用使用 spring-boot-maven-plugin 快速打包,构建一个可执行 jar。Spring Boot 内嵌容器,通过 java -jar 命令便可以直接启动应用,可以部署到生产环境。

虽然是一个简单的启动命令,背后却藏着很多知识,所谓 一花一世界,一叶一森林 。今天带着大家探索 FAT JAR 启动的背后原理。本文主要包含以下几个部分:

  • JAR 是什么。首先需要了解 jar 是什么,才知道 java -jar 做了什么事情。

  • FatJar 有什么不同。   Spring Boot 提供的可执行 jar 与普通的 jar 有什么区别。

  • 启动时的类加载原理。 启动过程中类加载器做了什么?Spring Boot 又如何通过自定义类加载器解决内嵌包的加载问题。

  • 启动的整个流程。最后整合前面三部分的内容,解析源码看如何完成启动。

JAR 是什么 

看过同事写了一篇关于 JAR 的介绍,里面的一句话深有感触。“ 在 JAVA 语言这个圈子里摸爬滚打,除了对这个语言层面和框架层面的学习之外,有一些东西一直存在,但确实没有对它们有足够的重视,因为都觉得是理所应当的,比如JAR 是什么?

JAR文件(Java归档,英语: J ava AR chive)是一种软件包文件格式,通常用于聚合大量的Java类文件、相关的元数据和资源(文本、图片等)文件到一个文件,以便分发Java平台应用软件或库。简单点理解其实就是一个压缩包。既然是压缩包那么为了提取JAR文件的内容,可以使用任何标准的unzip解压缩软件提取内容。或者使用Java虚拟机自带命令 jar -xf foo.jar来解压相应的jar文件。

JAR 可以简单分为两类:

  • 非可执行 JAR。这类就是平时依赖的类库。

  • 可执行 JAR。这类 JAR 可以通过 java -jar 启动。如 Spring Boot 编译的 JAR 就属于这一类。

不管是非可行 JAR 和 可执行 JAR 解压后都包含两部分:META-INF 目录(元数据)和 package 目录(编译后的class)。这种普通的 jar 不包含第三方依赖包,只包含应用自身的配置文件、class 等。

VJ7bYnI.png!web

不管是怎么类型的jar,都会有 MANIFEST.MF文件定义了包信息。首先看下非可执行 JAR guava 的 MANINFEST.MF 内容:

YvUjIvZ.png!web

自己构建一个可执行 jar 解压后的 MANINFEST.MF 的内容是:

3iMj22A.png!web

可执行JAR 的 MANINFEST.MF 文件比非执行jar多了一个 Main-Class, 这个配置表示jar 执行的路口类,当执行 java -jar xxx.jar 时会加载 Main-Class 指定的类执行。

FatJar 有什么不同

一般普通的 jar 只包含当前 jar的信息,不含有第三方 jar。当内部依赖第三方 jar 时,直接运行则会报错,这时候需要将第三方 jar 内嵌到 可执行 jar 里。将一个jar及其依赖的三方jar全部打到一个包中,这个包即为 FatJar

Spring Boot 为了解决内嵌 jar 问题,提供了一套 FatJar 解决方案,分别定义了 jar目录结构和 MANIFEST.MF。在编译生成可执行 jar 的基础上,使用spring-boot-maven-plugin 按Spring Boot 的可执行包标准 repackage,得到可执行的Spring Boot jar。根据可执行 jar 类型,分为两种: 可执行 Jar可执行 war

Spring Boot 项目被编译以后,在 targert 目录下存在两个jar文件:一个是 xxx.jar  和 xxx.jar.original,例如:

xxx.jar.original 是 maven 编译后的原始jar 文件,即标准的java jar。该文件仅包含应用本地资源。 如果单纯使用这个jar,无法正常运行,因为缺少依赖的第三方资源。因此 spring-boot-maven-plugin 插件对这个 xxx.jar.original 再做一层加工,引入第三方依赖的 jar 包等资源,将其 " repackage " 为 xxx.jar。可执行 Jar 的文件结构如下图所示:

6ZbAry6.png!web

  • META-INF: 存放元数据。MANIFEST.MF 是 jar 规范,Spring Boot 为了便于加载第三方 jar 对内容做了修改。

  • org: 存放Spring Boot 相关类,比如启动时所需的 Launcher 等。

  • BOOT-INF/class:存放应用编译后的 class 文件。

  • BOOT-INF/lib:存放应用依赖的 JAR 包。

Spring Boot 的 MANIFEST.MF 和普通jar 有些不同。

MjYBNnM.png!web

Main-Class: 是java -jar 启动引导类,但这里不是项目中的类,而是Spring Boot 内部的 JarLauncher。

Start-Class:这个才是正在要执行的应用内部主类

所以 java -jar 启动的时候,加载运行的是 JarLauncher。Spring Boot 内部如何通过 JarLauncher 加载 Start-Class 执行呢?为了更清楚加载流程,我们先介绍下 java -jar 是如何完成类加载逻辑的。

启动时的类加载原理

后面会有专门文章详细介绍类加载机制。 这里简单说下 java -jar 启动时是如何完成记载类加载的。 java 采用了双亲委派机制,Java语言系统自带有三个类加载器: 

  1. Bootstrap CLassloder:   最顶层的加载类,主要加载核心类库

  2. Extention ClassLoader: 扩展的类加载器,加载目录%JRE_HOME%/lib/ext目录下的jar包和class文件。 还可以加载-D java.ext.dirs选项指定的目录。

  3. AppClassLoader: 是应用加载器。

默认情况下通过 java -classpath,  java -cp, java -jar 使用的类加载器 都是 AppClassLoader。 普通可执行 jar 通过 java -jar 启动后,使用 AppClassLoader 加载 Main-class 类。 如果第三方jar 不在 AppClassLoader 里,会导致启动时候会报 ClassNotFoundException。

例如在 Spring Boot 可执行jar 的解压目录下,执行应用的主函数,就直接报该错误:

ziIvQnf.jpg!web

因为 java 命令未指明 classpath, 依赖的第三方jar 无法被加载。Spring Boot JarLauncher 启动的时,会将所有依赖的内嵌 jar (BOOT-INF/lib 目录下) 和 class (BOOT-INF/classes 目录)都加入到自定义的类加载器 LaunchedURLClassLoader中,并用这个 ClassLoder 去加载MANIFEST.MF 配置 Start-Class,则不会出现类找不到的错误。

LaunchedURLClassLoader 是 URLClassLoader 的子类, URLClassLoader  会通过URL[] 来搜索类所在的位置。Spring Boot 则将所需要的内嵌文档组装成 URL[],最终构建 LaunchedURLClassLoader 类。

启动的整个流程 

有了以上知识的铺垫,我们看下整个 FatJar 启动的过程会是怎样。 为了看源码可以在 pom.xml 引入下面的配置:

rYna6vn.png!web

简单概括起来可以分为几步:

  • java -jar 启动,AppClassLoader 则会加载  MANIFEST.MF 配置的 Main-Class,  JarLauncher。

  • JarLauncher 启动时,注册 URL 关联协议。

  • 获取所有内嵌的存档(内嵌 jar 和  class)

  • 根据存档的 URL[] 构建 类加载器。

  • 然后用这个类加载器加载 Start-Class 。 保证这些类都在同一个 ClassLoader 中。

U7zInaF.png!web

注册URL关联协议

因为FatJar 中的jar 都是内嵌的jar, 和普通 jar 有些不同。LaunchedURLClassLoader 是 URLClassLoader 类型的加载器,通过覆盖 jar 协议,使用自定义的jar  Handler。具体这块细节会放在类加载器里,敬请关注。

Spring Boot 解析系列文章

jMJr2me.png!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK