1.FAT JAR目录结构
解压后结果
drwxr-xr-x 5 hjq staff 160 Dec 3 09:57 .
drwxr-xr-x 10 hjq staff 320 Dec 4 11:42 ..
drwxr-xr-x 5 hjq staff 160 Dec 2 23:41 BOOT-INF
drwxr-xr-x 5 hjq staff 160 Dec 2 23:41 META-INF
drwxr-xr-x 3 hjq staff 96 Feb 1 1980 org
//BOOT-INF目录下
drwxr-xr-x 5 hjq staff 160 Dec 2 23:41 .
drwxr-xr-x 5 hjq staff 160 Dec 3 09:57 ..
drwxr-xr-x 5 hjq staff 160 Dec 2 23:41 classes
-rw-r--r-- 1 hjq staff 5466 Dec 2 23:41 classpath.idx
drwxr-xr-x 166 hjq staff 5312 Dec 2 23:41 lib
//META-INF目录下
drwxr-xr-x 5 hjq staff 160 Dec 2 23:41 .
drwxr-xr-x 5 hjq staff 160 Dec 3 09:57 ..
-rw-r--r-- 1 hjq staff 399 Dec 2 23:41 MANIFEST.MF
drwxr-xr-x 3 hjq staff 96 Dec 2 23:41 maven
-rw-r--r-- 1 hjq staff 109 Dec 2 17:57 spring.factories
2查看MANIFEST.MF
已知jar采用java的Fat jar启动规范。是读取MANIFEST.MF文件
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Archiver-Version: Plexus Archiver
Built-By: hjq
Start-Class: com.hjq.whyshare.home.DemoApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.3.2.RELEASE
Created-By: Apache Maven 3.6.1
Build-Jdk: 1.8.0_131
Main-Class: org.springframework.boot.loader.JarLauncher
命令行java-jar 会读取到Main-Class,作为启动类。
证明了main入口,在JarLauncher上
观察到Start-Class是我们编写的程序入口。查看其中实现逻辑。
JarLauncher的依赖,在pom上并没有添加,是spring-boot-maven-plugin插件打包时添加。
为了查看源码分析,添加引导项的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
</dependency>
3.分析JarLauncher实现原理
查看类继承关系。可知spring-boot打包有两种,传统的war包和jar包。该篇文章主要分析jar包方式。
3.1JarLauncher的main方法分析
//JarLauncher的main方法
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
//构建JarLauncher对象前,先执行父类ExecutableArchiveLauncher构造函数
public class JarLauncher extends ExecutableArchiveLauncher
public ExecutableArchiveLauncher() {
try {
this.archive = createArchive();
this.classPathIndex = getClassPathIndex(this.archive);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
archive是一个springboot loader库的类,表示档案。当生成jar时,createArchive()是指向该jar包的对象。后续archive上读取MANIFEST.MF等信息。以下是源码
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);
}
//ExplodedArchive是文件以解压后的形式运行;JarFileArchive是以jar的形式
return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
}
再查看launch()实现源码
protected void launch(String[] args) throws Exception {
//jar包形式才执行
if (!isExploded()) {
//用于系统设置java.protocol.handler.pkgs为org.springframework.boot.loader
JarFile.registerUrlProtocolHandler();
}
//Iterator迭代器是用于迭代jar包中的第三方依赖包,返回包含依赖包的classLoader。
ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
String jarMode = System.getProperty("jarmode");
//从MANIFEST.MF上读取到start-class配置
String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
launch(args, launchClass, classLoader);
}
protected String getMainClass() throws Exception {
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE);
}
if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
}
return mainClass;
}
获取到包含依赖的classLoader,启动参数还有我们编写的启动类(start-class),就会采用反射进行调用。
//基类Launcher的实现
protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
//调用业务编写的启动类入口main
createMainMethodRunner(launchClass, args, classLoader).run();
}
//MainMethodRunner类
public MainMethodRunner(String mainClass, String[] args) {
this.mainClassName = mainClass;
this.args = (args != null) ? 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(null, new Object[] { this.args });
}
从run()方法,可以看到,通过反射start-class对应的类,再调用main方法。
这个过程中最复杂的实现是JarFileArchive的内部实现,读取MANIFEST.MF,加载第三方依赖。后续再详细分析。