参考文档:
- https://docs.spring.io/spring-boot/docs/2.2.6.RELEASE/reference/html/appendix-executable-jar-format.html#executable-jar
- 《Spring Boot编程思想(核心篇)》
Spring Boot可执行Jar结构
普通项目可执行Jar结构如下:
example.jar
|
+-META-INF
| +-MANIFEST.MF
+-mycompany
| +-project
| +-YourClasses.class
其中MANIFEST.MF内容类似
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: XXXgaoch
Created-By: Apache Maven 3.6.1
Build-Jdk: 1.8.0_144
Main-Class: mycompany.project.YourClasses.class
而Spring Boot的Jar包结构如下
example.jar
|
+-META-INF
| +-MANIFEST.MF
+-org
| +-springframework
| +-boot
| +-loader
| +-<spring boot loader classes>
+-BOOT-INF
+-classes
| +-mycompany
| +-project
| +-YourClasses.class
+-lib
+-dependency1.jar
+-dependency2.jar
注意,这里和普通可执行Jar的区别
- 多了一个org\springframework\boot\loader文件夹
- 我们自己的代码放到了BOOT-INF\classes下
- 我们依赖的Jar放到了BOOT-INF\lib下
其中MANIFEST.MF内容类似
Manifest-Version: 1.0
Implementation-Title: springboot
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: mycompany.project.YourClasses
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.2.6.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher
注意,这里和普通可执行Jar的MANIFEST.MF文件区别
- Main函数所在类变成了org.springframework.boot.loader.JarLauncher
- 多了一个Start-Class,指向SpringBoot应用的入口
- 多了Spring-Boot-Classes,指向的目录BOOT-INF/classes/包含所有编译后的代码class和资源文件
- 多了个Spring-Boot-Lib,指向的目录BOOT-INF/lib/包含所有项目添加的依赖包
探寻JarLauncher
从MANIFEST.MF可得知Jar的启动类变成了org.springframework.boot.loader.JarLauncher,这个类所在的包可以通过Maven找到
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
JarLauncher源码如下
// 继承自ExecutableArchiveLauncher,后面我们再详细看下ExecutableArchiveLauncher
public class JarLauncher extends ExecutableArchiveLauncher {
// 定义默认Jar中Class所在文件夹
static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
// 定义默认Jar中依赖的类库所在文件夹
static final String BOOT_INF_LIB = "BOOT-INF/lib/";
public JarLauncher() {
}
protected JarLauncher(Archive archive) {
super(archive);
}
@Override
protected boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
}
// JVM将调用本启动方法,并把启动参数传递到args
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
}
类中最关键的是new JarLauncher().launch(args),其代码如下
protected void launch(String[] args) throws Exception {
// 注册Spring Boot的LaunchedURLClassLoader,提供读取从Jar包中内部jar包的能力,也就是BOOT-INF/lib下的Jar包
JarFile.registerUrlProtocolHandler();
// 创建LaunchedURLClassLoader实例
ClassLoader classLoader = createClassLoader(getClassPathArchives());
// getMainClass()获取MANIFEST.MF文件中定义的Start-Class,也就是我们编写的Boot启动类
launch(args, getMainClass(), classLoader);
}
getMainClass() 内容如下:
@Override
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);
}
return mainClass;
}
lauch创建MainMethodRunner实例,并调用其run方法,来实现对MainClass的调用,并传入启动参数。
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(mainClass, args, classLoader).run();
}
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) {
this.mainClassName = mainClass;
this.args = (args != null) ? args.clone() : null;
}
public void run() throws Exception {
Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.invoke(null, new Object[] { this.args });
}
}