springboot的jar为何能独立运行

本文探讨了`java -jar`命令的工作原理,通过分析SpringBoot的jar包`springbootstarterdemo-0.0.1-SNAPSHOT.jar`,揭示了Main-Class与Start-Class的区别以及它们在启动过程中的作用。主要关注点在于`JarLauncher`类如何根据`Start-Class`找到并执行实际的启动类`SpringbootstarterdemoApplication`。
摘要由CSDN通过智能技术生成

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文件:

在这里插入图片描述

  1. 打开上图红框中的文件,内容如下:

Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx

Implementation-Title: springbootstarterdemo

Implementation-Version: 0.0.1-SNAPSHOT

Start-Class: com.bolingcavalry.springbootstarterdemo.Springbootstarter

demoApplication

Spring-Boot-Classes: BOOT-INF/classes/

Spring-Boot-Lib: BOOT-INF/lib/

Build-Jdk-Spec: 1.8

Spring-Boot-Version: 2.3.1.RELEASE

Created-By: Maven Jar Plugin 3.2.0

Implementation-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位于最顶层,是个抽象类:

在这里插入图片描述

  1. 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 && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();

launch(args, launchClass, classLoader);

}

  1. 可见要重点关注的是getMainClass()方法,在看这个方法之前,我们先去关注一个重要的成员变量archive,是JarLauncher的父类ExecutableArchiveLauncher的archive,如下可见,该变量又来自方法createArchive:

public ExecutableArchiveLauncher() {

try {

this.archive = createArchive();

this.classPathIndex = getClassPathIndex(this.archive);

}

catch (Exception ex) {

throw new IllegalStateException(ex);

}

}

  1. 方法来自Launcher.createArchive,如下所示,可见成员变量archive实际上是个JarFileArchive对象:

protected final Archive createArchive() throws Exception {

ProtectionDomain protectionDomain = getClass().getProtectionDomain();

CodeSource codeSource = protectionDomain.getCodeSource();

URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;

String path = (location != null) ? location.getSchemeSpecificPart() : null;

if (path == null) {

throw new IllegalStateException(“Unable to determine code source archive”);

}

File root = new File(path);

if (!root.exists()) {

throw new IllegalStateException("Unable to determine code source archive from " + root);

}

return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));

}

  1. 现在回到getMainClass()方法,可见this.archive.getManifest方法返回的是META-INF/MANIFEST.MF文件的内容,然后getValue(START_CLASS_ATTRIBUTE)方法实际上就是从META-INF/MANIFEST.MF中取得了Start-Class的属性:

@Override

protected String getMainClass() throws Exception {

// 对应的是JarFileArchive.getManifest方法,

// 进去后发现对应的就是JarFile.getManifest方法,

// JarFile.getManifest对应的就是META-INF/MANIFEST.MF文件的内容

Manifest manifest = this.archive.getManifest();

String mainClass = null;

if (manifest != null) {

// 对应的是META-INF/MANIFEST.MF文件中的Start-Class的属性

mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE);

}

if (mainClass == null) {

throw new IllegalStateException("No ‘Start-Class’ manifest entry specified in " + this);

}

return mainClass;

}

  1. 从上述分析可知:getMainClass()方法返回的是META-INF/MANIFEST.MF中取得了Start-Class的属性com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication,再次回到launch方法中,可见最终运行的代码是launch(args, launchClass, classLoader),它的launchClass参数就是com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication:

protected void launch(String[] args) throws Exception {

if (!isExploded()) {

JarFile.registerUrlProtocolHandler();

}

ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());

String jarMode = System.getProperty(“jarmode”);

// 这里的launchClass等于"com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication"

String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();

// 这里就是启动SpringbootstarterdemoApplication的地方

launch(args, launchClass, classLoader);

}

  1. 展开launch(args, launchClass, classLoader),最终查到了MainMethodRunner类:

public class MainMethodRunner {

private final String mainClassName;

private final String[] args;

/**

  • Create a new {@link MainMethodRunner} instance.

  • @param mainClass the main class

  • @param args incoming arguments

*/

public MainMethodRunner(String mainClass, String[] args) {

// mainClassName被赋值为"com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication"

this.mainClassName = mainClass;

this.args = (args != null) ? args.clone() : null;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值