SpringBoot源码系列:
一文搞懂Spring Boot中java -jar启动jar包的原理
一文搞懂SpringBoot启动流程及自动配置
一文搞懂SpringBoot内嵌的Tomcat
一文搞懂SpringApplication对象的构建及spring.factories的加载时机
一文搞懂Spring Boot 事件监听机制

今天我们研究一下Spring Boot中通过java -jar启动jar包原理
1、Spring Boot中的jar包与普通jar包有什么不同?

Spirng Boot中打出来的jar只能运行,不能被其他项目依赖。Spirng Boot打出来的jar中\BOOT-INF\classes目录下的才是我们的代码。
普通的jar可以被其他项目引用,解压后就是包名,包里就是我们的代码

2、Spring Boot打包出来的jar包

springbootdemo-0.0.1-SNAPSHOT.jar
springbootdemo-0.0.1-SNAPSHOT.jar.original

springbootdemo-0.0.1-SNAPSHOT.jar是spring-boot-maven-plugin生成的jar包。包含了应用的第三方依赖,spring boot相关的类,存在嵌套的jar包,称之为executable jar或fat jar。springbootdemo-0.0.1-SNAPSHOT.jar.original是默认的maven-jar-plugin生成的包,仅包含编译用的本地资源。
在这里插入图片描述
3、MANIFEST.MF文件文件内容(下面源码分析中getMainClass()实际就是获取了该文件中的Start-Class属性的属性值)

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

4、Debug jar包的配置。
在这里插入图片描述
直接Debug该jar包即可
在这里插入图片描述

4、当我们执行java -jar命令时他会找到/META-INF/MANIFEST.MF文件中的Main-Class属性,从而找到应用程序执行入口类org.springframework.boot.loader.JarLauncher,该类中定义了main()从而我们找到了程序入口。在该方法中构建了JarLauncher并调用了launch()方法。

public static void main(String[] args) throws Exception {
		new JarLauncher().launch(args);
	}

实际调用的是Launcher中的launch(String[] args)方法。因为JarLauncher继承了ExecutableArchiveLauncher,而ExecutableArchiveLauncher又继承了Launcher。
在这里插入图片描述
接下来我们看一下这个launch(String[] args)方法。

protected void launch(String[] args) throws Exception {
		if (!isExploded()) {
		   // Launcher(启动器)通过exploded mode启动
		   //registerUrlProtocolHandler()注册一个java.protocol.handler.pkgs属性以便找到URLStreamHandler来处理jar URLs
		   //该方法捕获了Jar上下文URL,且通过java.protocol.handler.pkgs属性设置了org.springframework.boot.loader
			JarFile.registerUrlProtocolHandler();
		}
		//getClassPathArchivesIterator()返回用于构造类路径的Archive(即是一个归档文件,通常是一个tar/zip文件,jar是zip格式)
		//createClassLoader()根据Archive中的URLS创建LaunchedURLClassLoader
		ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
		//获取jarmode属性
		String jarMode = System.getProperty("jarmode");
		//此时jarmode属性为空,通过getMainClass()从Manifest中获取Start-Class(启动类)的值
		String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
		launch(args, launchClass, classLoader);
	}

继续关注上述launch(String[] args)方法中的最后一步launch(args, launchClass, classLoader)方法。

protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {
		//设置当前线程的类加载器为LaunchedURLClassLoader
		Thread.currentThread().setContextClassLoader(classLoader);
		//创建MainMethodRunner对象,调用它的run()方法来运行我们DemoApplication中的main()方法。	
		createMainMethodRunner(launchClass, args, classLoader).run();
	}

我们看一下MainMethodRunner中的run()方法是如何调用我们DemoApplication中的main()方法。

public void run() throws Exception {
        //首先通过反射获取到了main()方法所在主类
		Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
		//根据这个主类获取其中的main()方法
		Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
		//取消安全检查提升反射速度
		mainMethod.setAccessible(true);
		//实现main()动态调用
		mainMethod.invoke(null, new Object[] { this.args });
	}

至此Spring Boot中 java -jar启动jar包的整个流程结束,接下来进入整个Spring Boot项目的启动流程。

下面我们总结一下Spring Boot中 java -jar启动jar包的整个流程
请添加图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐