编程知识 cdmana.com

springboot的jar为何能独立运行

欢迎访问我的GitHub

https://github.com/zq2599/blog_demos

内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等;

欢迎访问我的GitHub

https://github.com/zq2599/blog_demos
内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等;

能独立运行的jar文件

在开发springboot应用时,通过java -jar命令启动应用是常用的方式,今天就来一起了解这个简单操作背后的技术;

开发demo

开发一个springboot应用作为本次研究的对象,对应的版本信息如下:

  • JDK:1.8.0_211
  • springboot:2.3.1.RELEASE
  • maven:3.6.0

接下来开发springboot应用,这个应用非常简单:

  1. springboot应用名为springbootstarterdemo,pom.
<?
  1. 只有一个java类,里面有个http接口:
package com.bolingcavalry.springbootstarterdemo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.Date;@SpringBootApplication@RestControllerpublic class SpringbootstarterdemoApplication { public static void main(String[] args) { SpringApplication.run(SpringbootstarterdemoApplication.class, args); } @RequestMapping(value = "/hello") public String hello(){ return "hello " + new Date(); }}
  1. 编码完成,在pom.
mvn clean package -U -DskipTests
  1. 构建成功后,在target目录下得到文件springbootstarterdemo-0.0.1-SNAPSHOT.jar
  2. 就是这个springbootstarterdemo-0.0.1-SNAPSHOT.jar,此时执行java -jar springbootstarterdemo-0.0.1-SNAPSHOT.jar就能启动应用,如下图:
    在这里插入图片描述
    接下来就用这个springbootstarterdemo-0.0.1-SNAPSHOT.jar来分析jar文件能够独立启动的原因;

java -jar做了什么

  • 先要弄清楚java -jar命令做了什么,在oracle官网找到了该命令的描述:

    If the -jar option is specified, its argument is the name of the JAR file containing class and resource files for the application. The startup class must be indicated by the Main-Class manifest header in its source code.

  • 再次秀出我蹩脚的英文翻译:

  1. 使用-jar参数时,后面的参数是的jar文件名(本例中是springbootstarterdemo-0.0.1-SNAPSHOT.jar);
  2. 该jar文件中包含的是class和资源文件;
  3. 在manifest文件中有Main-Class的定义;
  4. Main-Class的源码中指定了整个应用的启动类;(in its source code)

小结一下:
java -jar会去找jar中的manifest文件,在那里面找到真正的启动类;

探查springbootstarterdemo-0.0.1-SNAPSHOT.jar

  1. springbootstarterdemo-0.0.1-SNAPSHOT.jar是前面的springboot工程的构建结果,是个压缩包,用常见的压缩工具就能解压,我这里的环境是MacBook Pro,用unzip即可解压;
  2. 解压后有很多内容,我们先关注manifest相关的,下图红框中就是manifest文件:
    在这里插入图片描述
  3. 打开上图红框中的文件,内容如下:
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idxImplementation-Title: springbootstarterdemoImplementation-Version: 0.0.1-SNAPSHOTStart-Class: com.bolingcavalry.springbootstarterdemo.Springbootstarter demoApplicationSpring-Boot-Classes: BOOT-INF/classes/Spring-Boot-Lib: BOOT-INF/lib/Build-Jdk-Spec: 1.8Spring-Boot-Version: 2.3.1.RELEASECreated-By: Maven Jar Plugin 3.2.0Implementation-Vendor: Pivotal Software, Inc.Main-Class: org.springframework.boot.loader.JarLauncher
  1. 在上述内容可见Main-Class的值org.springframework.boot.loader.JarLauncher,这个和前面的java官方文档对应上了,正是这个JarLauncher类的代码中指定了真正的启动类;

疑惑出现

  1. 在MANIFEST.MF文件中有这么一行内容:
Start-Class: com.bolingcavalry.springbootstarterdemo.Springbootstarter demoApplication
  1. 前面的java官方文档中,只提到过Main-Class ,并没有提到Start-Class;
  2. Start-Class的值是SpringbootstarterdemoApplication,这是我们的java代码中的唯一类,也只真正的应用启动类;
  3. 所以问题就来了:理论上看,执行java -jar命令时JarLauncher类会被执行,但实际上是SpringbootstarterdemoApplication被执行了,这其中发生了什么呢?

猜测

动手之前先猜一下,个人觉得原因应该如下:

  1. java -jar命令会启动JarLauncher;
  2. Start-Class是给JarLauncher用的;
  3. JarLauncher根据Start-Class找到了SpringbootstarterdemoApplication,然后执行它;

分析JarLauncher

  1. 先下载SpringBoot源码,我下载的是2.3.1版本,地址:https://github.com/spring-projects/spring-boot/releases/tag/v2.3.1.RELEASE
  2. JarLauncher所在的工程是spring-boot-loader,先弄明白JarLauncher的继承关系,如下图,可见JarLauncher继承自ExecutableArchiveLauncher,而ExecutableArchiveLauncher的父类Launcher位于最顶层,是个抽象类:

在这里插入图片描述
3. java -jar执行的是JarLauncher的main方法,如下,会实例化一个JarLauncher对象,然后执行其launch方法,并且将所有入参都带入:

public static void main(String[] args) throws Exception { new JarLauncher().launch(args);}
  1. 上面的launch方法在父类Launcher中:
protected void launch(String[] args) throws Exception { // 将jar解压后运行的方式叫做exploded mode // 如果是exploded mode,就不能支持通过URL加载jar // 如果不是exploded mode,就可以通过URL加载jar if (!isExploded()) { // 如果允许通过URL加载jar,就在此注册对应的处理类 JarFile.registerUrlProtocolHandler(); } // 创建classLoader ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator()); // jarmode是创建docker镜像时用到的参数,使用该参数是为了生成带有多个layer信息的镜像 // 这里暂时不关注jarmode String jarMode = System.getProperty("jarmode"); //如果没有jarmode参数,launchClass的值就来自getMainClass()返回 String launchClass = (jarMode != null && !j.........

版权声明
本文为[程序猿欧文]所创,转载请带上原文链接,感谢
https://my.oschina.net/mikeowen/blog/4839682

Scroll to Top