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、org,boot-inf是存放对应的应用服务的.class文件和maven依赖的jar包,包括启动类springboottestapplication,meta-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,而jarlauncher是jar的启动器,这个类是在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,反之则是不可以的,那么对于springboottestapplication是jarlauncher的子进程,还是处于同一层级呢?接下来我们来看看jarlauncher的原理。
jarlauncher实现引导原理
因为org.springframework.boot.loader.jarlauncher的类是在spring-boot-loader中,但是若想在idea中来看源码,需要在pom文件中引入如下配置:
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-loader</artifactid> <scope>provided</scope> </dependency>
在引入上面的配置文件后,便可以在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的实现,这个实现的原理也是比较复杂,后面如果有机会,会再写篇文章来进行说明。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 历史上文武双全的辛弃疾,最后结局怎么样?
下一篇: 我怎么就是颗白菜了