欢送拜访我的GitHub
https://github.com/zq2599/blog_demos
内容:所有原创文章分类汇总及配套源码,波及Java、Docker、Kubernetes、DevOPS等;
欢送拜访我的GitHub
https://github.com/zq2599/blog_demos
内容:所有原创文章分类汇总及配套源码,波及Java、Docker、Kubernetes、DevOPS等;
能独立运行的jar文件
在开发springboot利用时,通过<font color=”blue”>java -jar</font>命令启动利用是罕用的形式,明天就来一起理解这个简略操作背地的技术;
开发demo
开发一个springboot利用作为本次钻研的对象,对应的版本信息如下:
- JDK:1.8.0_211
- springboot:2.3.1.RELEASE
- maven:3.6.0
接下来开发springboot利用,这个利用非常简单:
- springboot利用名为<font color=”blue”>springbootstarterdemo</font>,pom.xml文件内容:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.bolingcavalry</groupId>
<artifactId>springbootstarterdemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springbootstarterdemo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 只有一个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
@RestController
public class SpringbootstarterdemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootstarterdemoApplication.class, args);
}
@RequestMapping(value = "/hello")
public String hello(){
return "hello " + new Date();
}
}
- 编码实现,在pom.xml所在目录执行命令
mvn clean package -U -DskipTests
- 构建胜利后,在target目录下失去文件<font color=”blue”>springbootstarterdemo-0.0.1-SNAPSHOT.jar</font>
- 就是这个<font color=”blue”>springbootstarterdemo-0.0.1-SNAPSHOT.jar</font>,此时执行<font color=”blue”>java -jar springbootstarterdemo-0.0.1-SNAPSHOT.jar</font>就能启动利用,如下图:
接下来就用这个<font color=”blue”>springbootstarterdemo-0.0.1-SNAPSHOT.jar</font>来剖析jar文件可能独立启动的起因;
java -jar做了什么
- 先要弄清楚<font color=”blue”>java -jar</font>命令做了什么,在oracle官网找到了该命令的形容:
<font color=”blue”>
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.
</font>
- 再次秀出我糟糕的英文翻译:
- 应用<font color=”blue”>-jar</font>参数时,前面的参数是的jar文件名(本例中是springbootstarterdemo-0.0.1-SNAPSHOT.jar);
- 该jar文件中蕴含的是class和资源文件;
- 在manifest文件中有<font color=”blue”>Main-Class</font>的定义;
- <font color=”blue”>Main-Class</font>的源码中指定了整个利用的启动类;(in its source code)
小结一下:
<font color=”blue”>java -jar</font>会去找jar中的manifest文件,在那外面找到真正的启动类;
探查springbootstarterdemo-0.0.1-SNAPSHOT.jar
- <font color=”blue”>springbootstarterdemo-0.0.1-SNAPSHOT.jar</font>是后面的springboot工程的构建后果,是个压缩包,用常见的压缩工具就能解压,我这里的环境是MacBook Pro,用unzip即可解压;
- 解压后有很多内容,咱们先关注manifest相干的,下图红框中就是manifest文件:
- 关上上图红框中的文件,内容如下:
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
- 在上述内容可见Main-Class的值<font color=”blue”>org.springframework.boot.loader.JarLauncher</font>,这个和后面的java官网文档对应上了,正是这个JarLauncher类的代码中指定了真正的启动类;
纳闷呈现
- 在MANIFEST.MF文件中有这么一行内容:
Start-Class: com.bolingcavalry.springbootstarterdemo.Springbootstarter
demoApplication
- 后面的java官网文档中,只提到过<font color=”blue”>Main-Class </font>,并没有提到<font color=”blue”>Start-Class</font>;
- Start-Class的值是SpringbootstarterdemoApplication,这是咱们的java代码中的惟一类,也只真正的利用启动类;
- 所以问题就来了:实践上看,执行java -jar命令时JarLauncher类会被执行,但实际上是SpringbootstarterdemoApplication被执行了,这其中产生了什么呢?
猜想
入手之前先猜一下,集体感觉起因应该如下:
- java -jar命令会启动JarLauncher;
- <font color=”blue”>Start-Class</font>是给JarLauncher用的;
- JarLauncher依据<font color=”blue”>Start-Class</font>找到了SpringbootstarterdemoApplication,而后执行它;
剖析JarLauncher
- 先下载SpringBoot源码,我下载的是2.3.1版本,地址:https://github.com/spring-pro…
- JarLauncher所在的工程是spring-boot-loader,先弄明确JarLauncher的继承关系,如下图,可见JarLauncher继承自ExecutableArchiveLauncher,而ExecutableArchiveLauncher的父类Launcher位于最顶层,是个抽象类:
- java -jar执行的是JarLauncher的main办法,如下,会实例化一个JarLauncher对象,而后执行其launch办法,并且将所有入参都带入:
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
- 下面的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);
}
- 可见要重点关注的是<font color=”blue”>getMainClass()</font>办法,在看这个办法之前,咱们先去关注一个重要的成员变量<font color=”blue”>archive</font>,是JarLauncher的父类ExecutableArchiveLauncher的archive,如下可见,该变量又来自办法<font color=”blue”>createArchive</font>:
public ExecutableArchiveLauncher() {
try {
this.archive = createArchive();
this.classPathIndex = getClassPathIndex(this.archive);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
- 办法来自Launcher.createArchive,如下所示,可见成员变量<font color=”blue”>archive</font>实际上是个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));
}
- 当初回到getMainClass()办法,可见<font color=”blue”>this.archive.getManifest</font>办法返回的是<font color=”red”>META-INF/MANIFEST.MF</font>文件的内容,而后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;
}
- 从上述剖析可知:getMainClass()办法返回的是META-INF/MANIFEST.MF中获得了<font color=”red”>Start-Class</font>的属性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);
}
- 开展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;
}
public void run() throws Exception {
// 失去SpringbootstarterdemoApplication的Class对象
Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
// 失去SpringbootstarterdemoApplication的main办法对象
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.setAccessible(true);
// 通过反射执行main办法
mainMethod.invoke(null, new Object[] { this.args });
}
}
终于,水落石出了;
小结
最初尽可能简短做个小结,先看jar是如何产生的,如下图,maven插件生成的jar文件中,有常见的class、jar,也有合乎java标准的MANIFEST.MF文件,并且,还在MANIFEST.MF文件中额定生成了名为<font color=”blue”>Start-Class</font>的配置,这外面是咱们编写的利用启动类<font color=”blue”>SpringbootstarterdemoApplication</font>:
启动类是<font color=”blue”>JarLauncher</font>,它是如何与MANIFEST.MF文件关联的呢?从下图能够看出,最终是通过JarFile类的成员变量<font color=”blue”>manifestSupplier</font>关联上的:
再来看看要害代码的执行状况,如下图:
至此,SpringBoot的jar独立运行的基本原理曾经分明,探索的过程中,除了相熟要害代码流程,还对jar中的文件有了更多理解,如果您正在学习SpringBoot,心愿本文能给您一些参考;
官网文档
- 最初附上SpringBoot官网文档,能够看到<font color=”blue”>Start-Class</font>形容信息:
- 上述文档明确提到:Start-Class定义的是理论的启动类,此时的您应该对所有都了然于胸,产生本该如此的感叹;
你不孤独,欣宸原创一路相伴
- Java系列
- Spring系列
- Docker系列
- kubernetes系列
- 数据库+中间件系列
- DevOps系列
欢送关注公众号:程序员欣宸
微信搜寻「程序员欣宸」,我是欣宸,期待与您一起畅游Java世界…
https://github.com/zq2599/blog_demos
发表回复