Spring Boot 的java -jar命令启动原理详解
导语
在运用SpringBoot后,我们基本上摆脱之前项目每次上线的时候把项目打成war包。当然也不排除一些奇葩的规定,必须要用war包上线,不过很多时候,我们对一些东西只是处在使用的阶段,并不会去深入的研究使用的原理是什么,这貌似也是大多数人的固定思维。
或许正是如此,总会有些没有固定思维的人会去积极的探索原理,当然这话不是说我是积极的,我其实也是只原理的搬运工。今天和大家来简单的说下SpringBoot的项目在运行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、org,BOOT-INF是存放对应的应用服务的.class文件和Maven依赖的jar包,包括启动类SpringBootTestApplication,META-INF下存放的是Maven相关的pom信息和MANIFEST.MF文件,org文件夹下存放的是Springbootloader模块编译的.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:MavenArchiver3.4.0 Main-Class:org.springframework.boot.loader.JarLauncher
从上面的配置文件中,可以看到Main-Class属性指向的Class为org.springframework.boot.loader.JarLauncher,而JarLauncher是JAR的启动器,这个类是在org/springframework/boot/loader/,然后可以看到项目所定义的启动类是指向Start-Class这个属性的。
JAR文件启动器——JarLauncher
在上面我们说了JarLauncher是JAR可执行的启动器,那么它和项目的启动类SpringBootTestApplication有什么关联呢?先给大家来个示例,先来到解压目录下执行命令:javaorg.springframework.boot.loader.JarLauncher ,然后便是如下界面:
C:\Users\elisha\Desktop\spring-boot-test-0.0.1-SNAPSHOT>javaorg.springframework.boot.loader.JarLauncher ._________ /\\/___'_____(_)______\\\\ (()\___|'_|'_||'_\/_`|\\\\ \\/___)||_)|||||||(_||)))) '|____|.__|_||_|_||_\__,|//// =========|_|==============|___/=/_/_/_/ ::SpringBoot::(v2.2.3.RELEASE) 2020-01-1814:28:19.866INFO3644---[main]c.s.boot.test.SpringBootTestApplication:StartingSpringBootTestApplicationonLAPTOP-R2NNI9CMwithPID3644(C:\Users\elisha\Desktop\spring-boot-test-0.0.1-SNAPSHOT\BOOT-INF\classesstartedbyelishainC:\Users\elisha\Desktop\spring-boot-test-0.0.1-SNAPSHOT)
从上面的执行接口可以看到项目引导类SpringBootTestApplication会被JarLauncher类进行引导,但是如果我们到BOOT-INF/class目录下,然后也执行java com.spring.boot.test.SpringBootTestApplication,会报SpringApplication的ClassNotFoundException这个错误,由此可以得知这是因为java命令未指定ClassPath。不过当前SpringBoot依赖的JAR文件都是存放在BOOT-INF/lib下,而org.springframework.boot.loader.JarLauncher会将JAR作为SpringBootTestApplication类库的依赖,这也就是为什么JarLauncher能引导SpringBootTestApplication,反之则是不可以的,那么对于SpringBootTestApplication是JarLauncher的子进程,还是处于同一层级呢?接下来我们来看看JarLauncher的原理。
JarLauncher实现引导原理
因为org.springframework.boot.loader.JarLauncher的类是在spring-boot-loader中,但是若想在IDEA中来看源码,需要在pom文件中引入如下配置:
org.springframework.boot spring-boot-loader provided
在引入上面的配置文件后,便可以在IDEA中查看源码了,使用CTRL+N命令来搜索JarLauncher类,那就来看下源码,如下:
publicclassJarLauncherextendsExecutableArchiveLauncher{ staticfinalStringBOOT_INF_CLASSES="BOOT-INF/classes/"; staticfinalStringBOOT_INF_LIB="BOOT-INF/lib/"; publicJarLauncher(){ } protectedJarLauncher(Archivearchive){ super(archive); } @Override protectedbooleanisNestedArchive(Archive.Entryentry){ if(entry.isDirectory()){ returnentry.getName().equals(BOOT_INF_CLASSES); } returnentry.getName().startsWith(BOOT_INF_LIB); } publicstaticvoidmain(String[]args)throwsException{ newJarLauncher().launch(args); } }
从上面的JarLauncher类中,可以看到两个常量:BOOT_INF_CLASSES、BOOT_INF_LIB,而它们又分别指向如下路径:BOOT-INF/classes/、BOOT-INF/lib/,并用isNestedArchive(Archive.Entryentry)方法进行判断(在SpringBoot中Archive,抽象出了Archive的概念,一个Archive可以是一个Jar(JarFileArchive)、也可以是一个目录(ExplodedArchive),在这里可以理解为Spring Boot抽象出来的同一访问资源层。),从isNestedArchive方法的参数Archive.Entry对象貌似为一个JAR文件中的资源,譬如application.properties,同时这个对象和JarEntry是类似的,其name属性(Archive.Entry#getName())便是Jar资源的相对路径。当application.properties资源在FATJAR目录下时,其实Archive.Entry#getName()就是/BOOT-INF/classes/application.properties,此时便符合startsWith方法的判断,所以isNestedArchive(Archive.Entryentry)便返回为true。当返回为false时,便说明FATJAR被解压到文件目录了,由此也说明了SpringBoot应用可以通过javaorg.springframework.boot.loader.JarLauncher 命令启动的原因了。
Archive.Entry的实现
上面说了在SpringBoot中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,这里简单看下这个方法的实现:
protectedvoidlaunch(String[]args)throwsException{ JarFile.registerUrlProtocolHandler(); ClassLoaderclassLoader=createClassLoader(getClassPathArchives()); launch(args,getMainClass(),classLoader); }
总结
本篇文章简单的讲解了一下,java-jar命令的一个执行的原理,首先说了下jar包目录和jar命令启动入口,然后说了下JAR文件启动器——JarLauncher和JarLauncher实现引导原理,最后说了下Archive.Entry的实现,这个实现的原理也是比较复杂,后面如果有机会,会再写篇文章来进行说明。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。