Spring Boot JAR 直接运行的技术原理
1. 可执行 JAR 的结构设计
Spring Boot 生成的 JAR 是可执行 Fat JAR,其结构与传统 JAR 不同:
嵌套依赖:所有第三方依赖 JAR 文件存储在 BOOT-INF/lib 目录下,避免外部依赖冲突。
自定义类加载器:通过 Spring Boot Loader(位于 org/springframework/boot/loader 目录)实现嵌套 JAR 的加载。
应用代码与配置:用户代码在 BOOT-INF/classes 目录,META-INF/MANIFEST.MF 定义入口类。
我们将SpringBoot的应用打包jar文件解压后,可以看到如下结构:
文件说明:
1、BOOT-INF: classes 目录包括应用内所有的class;lib 目录是所有依赖的jar包。
2、META-INF:增强型清单文件
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Archiver-Version: Plexus Archiver
Built-By: cz
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.zikao.exam.ZikaoExamApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.7.18
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_351
Main-Class: org.springframework.boot.loader.JarLauncher
3、org: jar包加载相关类(Loader Classes)
2. 核心机制:Spring Boot Loader
入口类 JarLauncher
MANIFEST.MF 中指定 Main-Class 为 JarLauncher,而非用户的主类。JarLauncher 负责启动流程:
JVM->>JarLauncher: main()
JarLauncher->>ClassLoader: 创建LaunchedURLClassLoader
ClassLoader->>JarLauncher: 返回加载器实例
JarLauncher->>Application: 反射调用main()
Application->>SpringApplication: run()
1、JarLauncher 中main方法开启启动;
public static void main(String[] args) throws Exception {
(new JarLauncher()).launch(args);
}
2、在初始化JarLauncher时,同时会初识化父类构造方法 ,获取到jar报的文档地址,加载所有的jar文件;
public ExecutableArchiveLauncher() {
try {
this.archive = this.createArchive();
this.classPathIndex = this.getClassPathIndex(this.archive);
} catch (Exception var2) {
throw new IllegalStateException(var2);
}
}
通过获取classpath.idx 文件,获取加载的所有jar包
protected ClassPathIndexFile getClassPathIndex(Archive archive) throws IOException {
if (archive instanceof ExplodedArchive) {
String location = this.getClassPathIndexFileLocation(archive);
return ClassPathIndexFile.loadIfPossible(archive.getUrl(), location);
} else {
return null;
}
}
private String getClassPathIndexFileLocation(Archive archive) throws IOException {
Manifest manifest = archive.getManifest();
Attributes attributes = manifest != null ? manifest.getMainAttributes() : null;
String location = attributes != null ? attributes.getValue("Spring-Boot-Classpath-Index") : null;
return location != null ? location : this.getArchiveEntryPathPrefix() + "classpath.idx";
}
3、 创建 LaunchedURLClassLoader,加载 BOOT-INF/lib 下的依赖 JAR。使用自定义类加载器 LaunchedURLClassLoader,支持从嵌套 JAR 中加载类资源,解决传统 JAR 无法加载嵌套依赖的问题。
protected void launch(String[] args) throws Exception {
if (!this.isExploded()) {
JarFile.registerUrlProtocolHandler();
}
ClassLoader classLoader = this.createClassLoader(this.getClassPathArchivesIterator());
String jarMode = System.getProperty("jarmode");
String launchClass = jarMode != null && !jarMode.isEmpty() ? "org.springframework.boot.loader.jarmode.JarModeLauncher" : this.getMainClass();
this.launch(args, launchClass, classLoader);
}
protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
List<URL> urls = new ArrayList(50);
while(archives.hasNext()) {
urls.add(((Archive)archives.next()).getUrl());
}
return this.createClassLoader((URL[])urls.toArray(new URL[0]));
}
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
return new LaunchedURLClassLoader(this.isExploded(), this.getArchive(), urls, this.getClass().getClassLoader());
}
4、获取应用的主方法 ,也就是在 META-INF(增强型清单文件 )文件中的Start-Class所对应的方法
protected String getMainClass() throws Exception {
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
} else {
return mainClass;
}
}
5、 通过反射调用用户主类(Start-Class 指定的 SpringApplication 类)的 main() 方法。
protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
this.createMainMethodRunner(launchClass, args, classLoader).run();
}
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
return new MainMethodRunner(mainClass, args);
}
3. Maven 插件支持
通过 spring-boot-maven-plugin 插件实现可执行 JAR 的生成:
repackage 目标:将标准 Maven 构建的 JAR 转换为 Fat JAR,嵌入依赖和 Spring Boot Loader。
配置示例:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
4. 嵌入式容器整合
Spring Boot 默认集成嵌入式 Web 服务器(如 Tomcat、Jetty),无需外部部署:
依赖内嵌:服务器代码作为依赖打包进 JAR 的 BOOT-INF/lib 目录。
自动启动:SpringApplication 启动时自动初始化嵌入式服务器并监听端口。
总结
Spring Boot JAR 可直接运行的核心原因:
Fat JAR 结构:整合依赖、类加载器和应用代码。
自定义类加载器:通过 JarLauncher 和 LaunchedURLClassLoader 加载嵌套 JAR。
插件支持:spring-boot-maven-plugin 生成可执行包。
嵌入式服务器:内置 Web 容器实现自包含部署。
这些机制共同实现了 java -jar 直接启动 Spring Boot 应用的便捷性。