欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-28 16:13:23
导语 在运用spring boot 后,我们基本上摆脱之前项目每次上线的时候把项目打成war包。当然也不排除一些奇葩的规定,必须要用war包上线,不过很多时候,我们对一些东西只是...

导语

在运用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的实现,这个实现的原理也是比较复杂,后面如果有机会,会再写篇文章来进行说明。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。