Spring Boot 启动 jar 的原理剖析

Spring Boot 最吸引人的地方是WEB应用直接通过 java -jar 即可启动。那 java -jar 是如何启动一个应用的呢?我们今天就来一探究竟,揭开背后的原理。

以最简单的 Spring Boot 应用为例:源码点击这里
SpringBootJarDemoApplication.java

@SpringBootApplication
public class SpringBootJarDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootJarDemoApplication.class, args);
    }

}

pom.xml 文件:

...
	<dependencies>
		...
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-loader</artifactId>
        </dependency>
	</dependencies>
	
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
...

jar 目录结构

使用 JD-GUI 软件查看打包后 jar 的目录结构:

BOOT-INF/classes:业务代码编译后文件
BOOT-INF/lib:依赖第三方 jar 包
META-INF/MANIFEST.MF:jar 文件描述信息(包含启动入口,后面详细分析)
org.springframework.boot.loader:Spring Boot 自定义的 ClassLoader

当命令中执行 java -jar 时,会读取 META-INF/MANIFEST.MF 文件,现在详细分析这个文件的内容。

Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: spring-boot-jar-demo
Implementation-Version: 0.0.1-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.example.springbootjardemo.SpringBootJarDemoApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.6.3
Created-By: Maven JAR Plugin 3.2.2
Main-Class: org.springframework.boot.loader.JarLauncher

Main-Class 是 jar 启动入口,可以看到并非是我们自己定义 SpringBootJarDemoApplication 入口类,而是 Spring 的 JarLauncher。Start-Class 才是我们自己定义的入口,这是为什么呢?现在对源码进行剖析。
注意:项目必须引入 spring-boot-loader 才可以查看到源码.

创建 ClassLoader 并加载 Class 文件

JarLauncher 继承了 ExecutableArchiveLauncher,并提供了一个 main 方法供 java 调用。
在这里插入图片描述

public class JarLauncher extends ExecutableArchiveLauncher {
	...
	// main 方法的入口
	public static void main(String[] args) throws Exception {
		new JarLauncher().launch(args);
	}
}

JarLauncher#launch() 方法是调用的父类 Launcher 的方法。

public abstract class Launcher {
	protected void launch(String[] args) throws Exception {
		JarFile.registerUrlProtocolHandler();
		// 创建一个 ClassLoader,加载 BOOT-INF/classes/ 和 BOOT-INF/lib/ 目录的jar包
		ClassLoader classLoader = createClassLoader(getClassPathArchives());
		// 通过反射执行 SpringBootJarDemoApplication#main() 方法
		launch(args, getMainClass(), classLoader);
	}
}

getClassPathArchives() 方法是获取 jar 路径集合。

	@Override
	protected List<Archive> getClassPathArchives() throws Exception {
		// 获取内嵌的 jar 包,封装到 Archive 对象中
		List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));
		...
		return archives;
	}

然后创建 LaunchedURLClassLoader,用于加载 Class 文件。

	protected ClassLoader createClassLoader(URL[] urls) throws Exception {
		return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
	}

启动 SpringBootJarDemoApplication#main() 方法

Launcher#launch(String[] args, String mainClass, ClassLoader classLoader) 负责启动 SpringBootJarDemoApplication#main(),下面剖析一下如何启动的。

// Launcher#launch(String[] args)
launch(args, getMainClass(), classLoader);

getMainClass() 方法负责获取启动类,即是 SpringBootJarDemoApplication 类。

public abstract class ExecutableArchiveLauncher extends Launcher {
	protected String getMainClass() throws Exception {
		// 读取文件:META-INF/MANIFEST.MF
		Manifest manifest = this.archive.getManifest();
		String mainClass = null;
		if (manifest != null) {
			// 读取配置:Start-Class(即:com.example.springbootjardemo.SpringBootJarDemoApplication)
			mainClass = manifest.getMainAttributes().getValue("Start-Class");
		}
		if (mainClass == null) {
			throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
		}
		return mainClass;
	}
}

然后,调用自身的重载方法 launch()

	protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
		// 设置类加载器
		Thread.currentThread().setContextClassLoader(classLoader);
		// 创建并启动类
		createMainMethodRunner(mainClass, args, classLoader).run();
	}

createMainMethodRunner() 负责创建 MainMethodRunner 类实例,然后调用 MainMethodRunner#run() 方法。

public class MainMethodRunner {
	public void run() throws Exception {
		Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
		// 通过反射获取到 SpringBootJarDemoApplication#main() 方法
		Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
		// 反射调用 main() 方法,正式启动类:SpringBootJarDemoApplication
		mainMethod.invoke(null, new Object[] { this.args });
	}
}

idea debug 技巧

maven 的 pom.xml 引入 spring-boot-loader 的 jar 包。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-loader</artifactId>
</dependency>

Maven 打包

然后,通过 JAR Application 的方式启动 jar。
在这里插入图片描述

参考文献

深入解析SpringBoot java-jar命令行启动原理
Spring 官方文档

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值