Spring Boot 的java -jar命令启动原理

导语

在运用Spring Boot 后,我们基本上摆脱之前项目每次上线的时候把项目打成war包。当然也不排除一些奇葩的规定,必须要用war包上线,不过很多时候,我们对一些东西只是处在使用的阶段,并不会去深入的研究使用的原理是什么,这貌似也是大多数人的固定思维。

或许正是如此,总会有些没有固定思维的人会去积极的探索原理,当然这话不是说我是积极的,我其实也是只原理的搬运工。今天和大家来简单的说下Spring Boot 的项目在运行Java -jar的原理。

 

jar包目录和jar命令启动入口

在正式开始之前,我们先来看看把jar包进行解压。然后用tree /f命令查看目录结构(由于笔者写博文时用的是window,所以用的是tree /f命令),由于目录结构太长,这里做了相应省略,如下:

├─BOOT-INF
│  ├─classes
│  │  │  application.properties
│  │  │
│  │  └─com
│  │      └─spring
│  │          └─boot
│  │              └─test
│  │                      SpringBootTestApplication.class
│  │
│  └─lib
│          classmate-1.5.1.jar
│          hibernate-validator-6.0.18.Final.jar
│          …………此处省略…………
│
├─META-INF
│  │  MANIFEST.MF
│  │
│  └─maven
│      └─com.spring.boot.test
│          └─spring-boot-test
│                  pom.properties
│                  pom.xml
│
└─org
    └─springframework
        └─boot
            └─loader
                │  ExecutableArchiveLauncher.class
                │  JarLauncher.class
                │  LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
                │  LaunchedURLClassLoader.class
                │  Launcher.class
                │  MainMethodRunner.class
                │  PropertiesLauncher$1.class
                │  PropertiesLauncher$ArchiveEntryFilter.class
                │  PropertiesLauncher$PrefixMatchingArchiveFilter.class
                │  PropertiesLauncher.class
                │  WarLauncher.class
                │
                ├─archive
                │      Archive$Entry.class
                │      …………此处省略…………
                │
                ├─data
                │      RandomAccessData.class
                │      …………此处省略…………
                │
                ├─jar
                │      AsciiBytes.class
                │      Bytes.class
                │      …………此处省略…………
                │
                └─util
                        SystemPropertyUtils.class

先简单说下上面目录结构,大体目录分三层:BOOT-INF、META-INF、orgBOOT-INF是存放对应的应用服务的.class文件和Maven依赖的jar包,包括启动类SpringBootTestApplicationMETA-INF下存放的是Maven相关的pom信息和MANIFEST.MF文件,org文件夹下存放的是Spring boot loader模块编译的.class文件,也就是jar启动的关键代码所在。

在执行java -jar命令的时候,它的启动类配置实在jar包目录下META-INF文件夹下的名MANIFEST.MF文件中,在这个文件中有一个名为Main-Class的属性,我们来看下这个文件的具体内容:

Manifest-Version: 1.0
Implementation-Title: spring-boot-test
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.spring.boot.test.SpringBootTestApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.2.3.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher

 

从上面的配置文件中,可以看到Main-Class属性指向的Class为org.springframework.boot.loader.JarLauncher,而JarLauncherJAR的启动器,这个类是在org/springframework/boot/loader/,然后可以看到项目所定义的启动类是指向Start-Class这个属性的。

 

JAR文件启动器——JarLauncher

在上面我们说了JarLauncher是JAR可执行的启动器,那么它和项目的启动类SpringBootTestApplication有什么关联呢?先给大家来个示例,先来到解压目录下执行命令:java org.springframework.boot.loader.JarLauncher ,然后便是如下界面:

C:\Users\elisha\Desktop\spring-boot-test-0.0.1-SNAPSHOT>java org.springframework.boot.loader.JarLauncher

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.3.RELEASE)

2020-01-18 14:28:19.866  INFO 3644 --- [           main] c.s.boot.test.SpringBootTestApplication  : Starting SpringBootTestApplication on LAPTOP-R2NNI9CM with PID 3644 (C:\Users\elisha\Desktop\spring-boot-test-0.0.1-SNAPSHOT\BOOT-INF\classes started by elisha in C:\Users\elisha\Desktop\spring-boot-test-0.0.1-SNAPSHOT)

从上面的执行接口可以看到项目引导类SpringBootTestApplication会被JarLauncher类进行引导,但是如果我们到BOOT-INF/class目录下,然后也执行java  com.spring.boot.test.SpringBootTestApplication,会报SpringApplication的ClassNotFoundException这个错误,由此可以得知这是因为java命令未指定Class Path。不过当前Spring Boot依赖的JAR文件都是存放在BOOT-INF/lib下,而org.springframework.boot.loader.JarLauncher会将JAR作为SpringBootTestApplication类库的依赖,这也就是为什么JarLauncher能引导SpringBootTestApplication,反之则是不可以的,那么对于SpringBootTestApplicationJarLauncher的子进程,还是处于同一层级呢?接下来我们来看看JarLauncher的原理。

 

JarLauncher实现引导原理

因为org.springframework.boot.loader.JarLauncher的类是在spring-boot-loader中,但是若想在IDEA中来看源码,需要在pom文件中引入如下配置:


    org.springframework.boot
    spring-boot-loader
    provided

在引入上面的配置文件后,便可以在IDEA中查看源码了,使用CTRL+N命令来搜索JarLauncher类,那就来看下源码,如下:

public class JarLauncher extends ExecutableArchiveLauncher {

	static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";

	static final String BOOT_INF_LIB = "BOOT-INF/lib/";

	public JarLauncher() {
	}

	protected JarLauncher(Archive archive) {
		super(archive);
	}

	@Override
	protected boolean isNestedArchive(Archive.Entry entry) {
		if (entry.isDirectory()) {
			return entry.getName().equals(BOOT_INF_CLASSES);
		}
		return entry.getName().startsWith(BOOT_INF_LIB);
	}

	public static void main(String[] args) throws Exception {
		new JarLauncher().launch(args);
	}

}

从上面的JarLauncher类中,可以看到两个常量:BOOT_INF_CLASSES、BOOT_INF_LIB,而它们又分别指向如下路径:BOOT-INF/classes/、BOOT-INF/lib/,并用isNestedArchive(Archive.Entry entry)方法进行判断(在Spring Boot中Archive,抽象出了Archive的概念,一个Archive可以是一个Jar(JarFileArchive)、也可以是一个目录(ExplodedArchive),在这里可以理解为Spring  Boot抽象出来的同一访问资源层。),从isNestedArchive方法的参数Archive.Entry对象貌似为一个JAR文件中的资源,譬如application.properties,同时这个对象和JarEntry是类似的,其name属性(Archive.Entry#getName())便是Jar资源的相对路径。当application.properties资源在FAT JAR目录下时,其实Archive.Entry#getName()就是/BOOT-INF/classes/application.properties,此时便符合startsWith方法的判断,所以isNestedArchive(Archive.Entry entry)便返回为true。当返回为false时,便说明FAT JAR被解压到文件目录了,由此也说明了Spring Boot应用可以通过java org.springframework.boot.loader.JarLauncher 命令启动的原因了。

 

Archive.Entry的实现

上面说了在Spring Boot中Archive,抽象出了Archive的概念,一个Archive可以是一个Jar(JarFileArchive)、也可以是一个目录(ExplodedArchive),这里所说的JarFileArchive、ExplodedArchive便是Archive的两种是想方式,对于这两个类的实现代码感兴趣额同学可以自己去看看。

不过由此也说明了JarLauncher  既支持JAR启动,又支持文件系统启动。同时JarLauncher 在作为引导类的时候,当执行java -jar 命令式,/META-INF/ 下MANIFEST.MF文件中的Main-Class属性将调用它的,main(String [])方法,其实它还是调用JarLauncher #launch(args)方法,这个方法是实现基类Launcher,这里简单看下这个方法的实现:

protected void launch(String[] args) throws Exception {
	JarFile.registerUrlProtocolHandler();
	ClassLoader classLoader = createClassLoader(getClassPathArchives());
	launch(args, getMainClass(), classLoader);
}

   

总结

本篇文章简单的讲解了一下,java -jar命令的一个执行的原理,首先说了下jar包目录和jar命令启动入口,然后说了下JAR文件启动器——JarLauncher和JarLauncher实现引导原理,最后说了下Archive.Entry的实现,这个实现的原理也是比较复杂,后面如果有机会,会再写篇文章来进行说明。

 

你可能感兴趣的:(Spring,Boot)