Spring Boot 最吸引人的地方是WEB应用直接通过 java -jar
即可启动。那 java -jar
是如何启动一个应用的呢?我们今天就来一探究竟,揭开背后的原理。
以最简单的 Spring Boot 应用为例:源码点击这里
SpringBootJarDemoApplication.java
@SpringBootApplication
public class SpringBootJarDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootJarDemoApplication.class, args);
}
}
pom.xml 文件:
...
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
...
jar 目录结构
使用 JD-GUI 软件查看打包后 jar 的目录结构:
BOOT-INF/classes:业务代码编译后文件
BOOT-INF/lib:依赖第三方 jar 包
META-INF/MANIFEST.MF:jar 文件描述信息(包含启动入口,后面详细分析)
org.springframework.boot.loader:Spring Boot 自定义的 ClassLoader
当命令中执行 java -jar
时,会读取 META-INF/MANIFEST.MF 文件,现在详细分析这个文件的内容。
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: spring-boot-jar-demo
Implementation-Version: 0.0.1-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.example.springbootjardemo.SpringBootJarDemoApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.6.3
Created-By: Maven JAR Plugin 3.2.2
Main-Class: org.springframework.boot.loader.JarLauncher
Main-Class 是 jar 启动入口,可以看到并非是我们自己定义 SpringBootJarDemoApplication 入口类,而是 Spring 的 JarLauncher。Start-Class 才是我们自己定义的入口,这是为什么呢?现在对源码进行剖析。
注意:项目必须引入 spring-boot-loader 才可以查看到源码.
创建 ClassLoader 并加载 Class 文件
JarLauncher 继承了 ExecutableArchiveLauncher,并提供了一个 main 方法供 java 调用。
public class JarLauncher extends ExecutableArchiveLauncher {
...
// main 方法的入口
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
}
JarLauncher#launch() 方法是调用的父类 Launcher 的方法。
public abstract class Launcher {
protected void launch(String[] args) throws Exception {
JarFile.registerUrlProtocolHandler();
// 创建一个 ClassLoader,加载 BOOT-INF/classes/ 和 BOOT-INF/lib/ 目录的jar包
ClassLoader classLoader = createClassLoader(getClassPathArchives());
// 通过反射执行 SpringBootJarDemoApplication#main() 方法
launch(args, getMainClass(), classLoader);
}
}
getClassPathArchives() 方法是获取 jar 路径集合。
@Override
protected List<Archive> getClassPathArchives() throws Exception {
// 获取内嵌的 jar 包,封装到 Archive 对象中
List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));
...
return archives;
}
然后创建 LaunchedURLClassLoader,用于加载 Class 文件。
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}
启动 SpringBootJarDemoApplication#main() 方法
Launcher#launch(String[] args, String mainClass, ClassLoader classLoader) 负责启动 SpringBootJarDemoApplication#main(),下面剖析一下如何启动的。
// Launcher#launch(String[] args)
launch(args, getMainClass(), classLoader);
getMainClass() 方法负责获取启动类,即是 SpringBootJarDemoApplication 类。
public abstract class ExecutableArchiveLauncher extends Launcher {
protected String getMainClass() throws Exception {
// 读取文件:META-INF/MANIFEST.MF
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
// 读取配置:Start-Class(即:com.example.springbootjardemo.SpringBootJarDemoApplication)
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
}
return mainClass;
}
}
然后,调用自身的重载方法 launch()
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
// 设置类加载器
Thread.currentThread().setContextClassLoader(classLoader);
// 创建并启动类
createMainMethodRunner(mainClass, args, classLoader).run();
}
createMainMethodRunner() 负责创建 MainMethodRunner 类实例,然后调用 MainMethodRunner#run() 方法。
public class MainMethodRunner {
public void run() throws Exception {
Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
// 通过反射获取到 SpringBootJarDemoApplication#main() 方法
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
// 反射调用 main() 方法,正式启动类:SpringBootJarDemoApplication
mainMethod.invoke(null, new Object[] { this.args });
}
}
idea debug 技巧
maven 的 pom.xml 引入 spring-boot-loader 的 jar 包。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
</dependency>
Maven 打包
然后,通过 JAR Application 的方式启动 jar。