SpringBootjar包启动分析

编程

Spring Boot jar包启动分析

首先,为了了解Spring Boot Jar包的启动情况,我们需要构建一个Spring的FAT jar 看看其中都有哪些东西。

解压Spring Boot Maven Plugin 打包的jar

Spring Boot项目打包后,通过mvn package方式,可以看到在代码的target目录下,生成了一个jar文件和一个jar.original文件。这两个文件有什么区别呢?

  • jar文件:这个是通过SpringBootMavenPlugin插件重新包装出来的,他里面包含了我们项目中所依赖的所有jar包文件
  • jar.original文件:这个是通过maven打包后最原始的文件,里面没有项目中所以来的jar包,因此这个文件通常比较小

我们通过比较解压后的文件结构来了解下两种jar的区别。

jar 文件

├─BOOT-INF

│ │ classpath.idx

│ │

│ ├─classes

│ │ │ application.yml

│ │ │

│ │ └─com

│ │ └─chillax

│ │ │ Application.class

│ │ │

│ │ └─controller

│ │ HelloController.class

│ │

│ └─lib

│ hutool-all-5.3.8.jar

│ .............jar

├─META-INF

│ │ MANIFEST.MF

│ │

│ └─maven

│ └─com.chillax

│ └─spring-boot-loader

│ pom.properties

│ pom.xml

└─org

└─springframework

└─boot

└─loader

│ ClassPathIndexFile.class

│ ...........class

├─archive

│ Archive$Entry.class

│ ..........class

├─data

│ ..........class

├─jar

│ AsciiBytes.class

│ .......class

├─jarmode

│ JarMode.class

│ ............class

└─util

SystemPropertyUtils.class

  • BOOT-INF/classes 目录存放应用编译后的class文件
  • BOOT-INF/lib 目录存放应用以来的jar包
  • MEA-INF目录存放应用的相关元信息。
  • org目录存放Spring Boot相关classs

jar.original 文件

│  application.yml

├─com

│ └─chillax

│ │ Application.class

│ │

│ └─controller

│ HelloController.class

└─META-INF

│ MANIFEST.MF

└─maven

└─com.chillax

└─spring-boot-loader

pom.properties

pom.xml

我们可以看到内容比较少,主要是少了,依赖的jar和Spring Boot相关classes。因为这个源文件会被Spring Boot的Maven插件重新打包,形成可运行的Spring Boot FatJar

FAT JAR执行模块-spring-boot-loader

当使用java -jar 命令时,按照官方文档提供的标准,默认的启动类必须要在/META-INF/MANIFEST.MF文件中指明启动的类。

jar 文件官方规范 参考这个链接 https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html

我们来看一下,Spring Boot项目中生成的清单文件是什么样子的

Manifest-Version: 1.0

Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx

Archiver-Version: Plexus Archiver

Built-By: chillax

Start-Class: com.chillax.Application

Spring-Boot-Classes: BOOT-INF/classes/

Spring-Boot-Lib: BOOT-INF/lib/

Spring-Boot-Version: 2.3.1.RELEASE

Created-By: Apache Maven 3.6.1

Build-Jdk: 1.8.0_251

Main-Class: org.springframework.boot.loader.JarLauncher

我们可以看到项目的相关元信息。可以看到Main-Class被插件包装为了JarLauncher。并且指定的Start-class为我们的最常见的SpringApplication写的入口类。那我们所得到的信息是,Spring Boot 通过打包插件,讲项目的元信息进行再次封装后,通过spring-boot-loader#JarLauncher类启动我们的Spring Boot的入口类,从而实现spring boot 项目的jar启动。

spring-boot-loader查看

这里面类的层级关系,我们可以通过idea查看到

一般情况下,我们的FatJAR再我们执行java -jar 后,会执行JarLauncher#main方法,然后通过JarLauncher的launch方法启动我们的应用。

/**

* Launch the application. This method is the initial entry point that should be

* called by a subclass {@code public static void main(String[] args)} method.

* @param args the incoming arguments

* @throws Exception if the application fails to launch

*/

protected void launch(String[] args) throws Exception {

if (!isExploded()) {

JarFile.registerUrlProtocolHandler();

}

ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());

String jarMode = System.getProperty("jarmode");

String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();

launch(args, launchClass, classLoader);

}

launch方法主要是执行了三部操作

  • 注册自己的Jar协议解析器

  • 加载自己的类加载器,用于加载自身classes和依赖的classes

    Archive

    SpringBoot定义了一个描述资源的接口org.springframework.boot.loader.archive.Archive

    • org.springframework.boot.loader.archive.ExplodedArchive

      • 用于在文件夹目录下寻找资源

    • org.springframework.boot.loader.archive.JarFileArchive

      • 用于在jar包环境下寻找资源。

    而在SpringBoot打包的fatJar中,则是使用后者。

    这里主要是解析Spring自己抽象的资源类,将所有关联的class加载

  • launch方法利用反射,启动MANIFEST.MF文件中指定的start-class的Main方法

为什么要注册自己的Jar协议解析器呢?

因为,默认情况下,JDK提供的ClassLoader只能识别Jar中的class文件以及加载classpath下的其他jar包中的class文件。对于在jar包中的jar包是无法加载的。

Spring Boot是如何重写协议解析器

首先,在java中描述一种资源,通常使用URL,与URL关联的协议通常有一个URLStreamHandler的实现类,

在JDK中可以找到file、http、jar的内置协议,这些实现类在sum.net.www.protocol下且类名必须为Handler

这些处理器的读取规则有如下几种:

  • 实现URLStreamHandlerFactory接口,通过方法URL.setURLStreamHandlerFactory设置。该属性是一个静态属性,且只能被设置一次。
  • 直接提供URLStreamHandler的子类,作为URL的构造方法的入参之一。但是在JVM中有固定的规范要求:
  • 子类的类名必须是 Handler ,同时最后一级的包名必须是协议的名称。比如自定义了Http的协议实现,则类名必然为xx.http.Handler
  • JVM 启动的时候,需要设置 java.protocol.handler.pkgs 系统属性,如果有多个实现类,那么中间用 | 隔开。因为JVM在尝试寻找Handler时,会从这个属性中获取包名前缀,最终使用包名前缀.协议名.Handler,使用Class.forName方法尝试初始化类,如果初始化成功,则会使用该类的实现作为协议实现。

因此,Spring Boot为了能够加载到当前jar中的class文件以及classpath下及其jar包下所有的class文件,spring官方扩展了URLStreamHandler实现了自己的jar读取协议该处理类在org.springframework.boot.loader.jar.Handler这里,感兴趣的可以去看看源码是怎么实现协议的扩展的。

以上是 SpringBootjar包启动分析 的全部内容, 来源链接: utcz.com/z/518241.html

回到顶部