大家都知道springboot应用打成jar包后可以直接运行,那jar包到底是怎么运行起来的呢?
首先我们可以把jar包打开看一下里面的目录结构:
META-INF:程序入口,其中MANIFEST.MF用于描述jar包的信息
BOOT-INF/lib:放置第三方依赖的jar包
BOOT-INF/classes:应用自己的代码org:spring boot loader相关的代码
org:spring boot loader相关的代码
我们先看一下META-INF/MANIFEST.MF文件里面都有些什么
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: spring-cloud
Implementation-Version: 0.0.1-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.java.springcloud.SpringCloudApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.4.0
Created-By: Maven Jar Plugin 3.2.0
Main-Class: org.springframework.boot.loader.JarLauncher
当我们使用java -jar 执行jar包的时候,会执行Main-Class指定类的main方法,org.springframework.boot.loader.JarLauncher这个类是springboot loader自己的一个类,我们来看一下里面的main方法具体实现
public static void main(String[] args) throws Exception {
(new JarLauncher()).launch(args);
}
launch方法的具体实现在org.springframework.boot.loader.Launcher类里,主要是创建自定义 ClassLoader 实现类 LaunchedURLClassLoader,通过它来加载 BOOT-INF/classes 目录下的类,以及 BOOT-INF/lib 目录下的 jar 包中的类,引入自定义类加载器就是为了能解决jar包嵌套jar包的问题,系统自带的AppClassLoarder不支持读取嵌套jar包
protected void launch(String[] args) throws Exception {
if (!this.isExploded()) {
JarFile.registerUrlProtocolHandler();
}
//创建自定义 ClassLoader 实现类 LaunchedURLClassLoader
ClassLoader classLoader = this.createClassLoader(this.getClassPathArchivesIterator());
String jarMode = System.getProperty("jarmode");
//获取运行的mainclass
String launchClass = jarMode != null && !jarMode.isEmpty() ? "org.springframework.boot.loader.jarmode.JarModeLauncher" : this.getMainClass();
this.launch(args, launchClass, classLoader);
}
protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
//执行自定义类的main方法
this.createMainMethodRunner(launchClass, args, classLoader).run();
}
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
return new MainMethodRunner(mainClass, args);
}
接下来我们看一下getMainClass方法,是怎么去加载我们自己的类,是通过读取MANIFEST.MF文件里的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;
}
}
MainMethodRunner类里主要是通过反射生成shi'l执行我们自定义类里的main方法
public class MainMethodRunner {
private final String mainClassName;
private final String[] args;
public MainMethodRunner(String mainClass, String[] args) {
this.mainClassName = mainClass;
this.args = args != null ? (String[])args.clone() : null;
}
public void run() throws Exception {
Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.setAccessible(true);
mainMethod.invoke((Object)null, this.args);
}
}