Spring Boot(1) 启动分析

本文深入探讨了SpringBoot可执行Jar包的结构特点,对比了与普通可执行Jar的不同之处,详细分析了MANIFEST.MF文件的配置项,以及JarLauncher类如何加载并启动SpringBoot应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考文档:

  1. https://docs.spring.io/spring-boot/docs/2.2.6.RELEASE/reference/html/appendix-executable-jar-format.html#executable-jar
  2. 《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的区别

  1. 多了一个org\springframework\boot\loader文件夹
  2. 我们自己的代码放到了BOOT-INF\classes下
  3. 我们依赖的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文件区别

  1. Main函数所在类变成了org.springframework.boot.loader.JarLauncher
  2. 多了一个Start-Class,指向SpringBoot应用的入口
  3. 多了Spring-Boot-Classes,指向的目录BOOT-INF/classes/包含所有编译后的代码class和资源文件
  4. 多了个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 });
	}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值