spring-boot Fat JAR启动原理

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,加载第三方依赖。后续再详细分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值