spring-boot-loader jar包文件分析以及springBoot启动流程

当使用jar加载springBoot项目时 使用

spring-bootloader.jar插件(23)

BOOT-INF

项目中编写的类和第三方jar
在这里插入图片描述
在这里插入图片描述

META-INF文件清单

在这里插入图片描述
MANIFEST.MF 清单文件 (文件结尾回车换行)
start-Class: com.meishi.sales.SalesBootstrap springboot 提供的类加载器 也就是@springBootAplication注解标示的类
Main-Class: org.springframework.boot.loader.JarLauncher 程序启动入口类

org

来自spring-boot-loader(用于项目的打包和加载)jar包编译之后的内容
在这里插入图片描述

jarLauncher源代码分析

JarLauncher 程序启动入口类 将其对应jar加载到内存中 类路径下

 */
public class JarLauncher extends ExecutableArchiveLauncher {

	static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";

	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()) {
			//如果返回的目录名字 != "BOOT-INF/classes/"
			return entry.getName().equals(BOOT_INF_CLASSES); 
		}
		//如果是一个文件 是不是以"BOOT-INF/lib/" 开头
		return entry.getName().startsWith(BOOT_INF_LIB);
	}

	public static void main(String[] args) throws Exception {
		//先通过无参构造判断是否为jar再创建归档文件archive再调用父类launcher的launch方法
		new JarLauncher().launch(args);
	}

}

launch()是一个初始的入口点,被其子类的main方法所调用

	protected void launch(String[] args) throws Exception {
		//处理器 处理jar文件的url
		JarFile.registerUrlProtocolHandler();
		//获取自定义类加载器UrlClassLoader
		ClassLoader classLoader = createClassLoader(getClassPathArchives());
		//加载main启动类(命令行参数,要被运行的主类,自定义的URLClassLoader)
		launch(args, getMainClass(), classLoader);
	}

getClassPathArchives()

protected List<Archive> getClassPathArchives() throws Exception {
		//将已经加载的内容放在list集合当中 classse和lib 自己编写的类和第三方jar
		List<Archive> archives = new ArrayList<>(
		//调用ExecutableArchiveLauncher类中isNestedArchive 抽象方法该方法被
		//JarLauncher 子启动器实现
				this.archive.getNestedArchives(this::isNestedArchive));
		//回调
		postProcessClassPathArchives(archives);
		//返回符合条件的加载内容"BOOT-INF/classes/" 和 "BOOT-INF/lib/"
		return archives;
	}

createClassLoader()创建一个加载指定归档文件的类加载器

	protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
		List<URL> urls = new ArrayList<>(archives.size());
		for (Archive archive : archives) {
		//添加其URL的完整路径 其就是将Archive文件对象转换成url
			urls.add(archive.getUrl());
		}
		//调用一个加载指定url的一个类加载器
		return createClassLoader(urls.toArray(new URL[0]));
	}

createClassLoader()

protected ClassLoader createClassLoader(URL[] urls) throws Exception {
		//SpringBoot创建的URL类加载器(url数组,和指定的父加载器)
		return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
	}

getMainClass()

	@Override
	protected String getMainClass() throws Exception {
		//获取文件清单的Mainifest如下图
		Manifest manifest = this.archive.getManifest();
		String mainClass = null;
		if (manifest != null) {
		//获取其中的"Start-Class" 指定的class类 也就是当前项目的启动类
			mainClass = manifest.getMainAttributes().getValue("Start-Class");
		}
		if (mainClass == null) {
			throw new IllegalStateException(
					"No 'Start-Class' manifest entry specified in " + this);
		}
		//返回
		return mainClass;
	}

在这里插入图片描述

jar文件规范

  • 启动类也就是:Main-Class: org.springframework.boot.loader.JarLauncher
    程序启动入口类
    指定的Main-Class类必须位于jar包目录顶层不能位于其他目录的子目录里面

在这里插入图片描述
在这里插入图片描述

  • jar文件不能被嵌套 反之为fatJar
    传统做法 就是将jar保重带的jar文件中的class类重新拷贝一份 如org中的class类就是其拷贝的
    问题
    1.jar文件内容混乱
    2.依赖的class类名同名不同包 拷贝会被复制
    springboot采用自定义类加载器的方式去加载嵌套jar
    当以 "BOOT-INF/lib/"开头springboot就知道这是引用第三方jar包在这里插入图片描述
    在这里插入图片描述
    createClassLoader()

**此时调用当前类的launch方法 **

protected void launch(String[] args, String mainClass, ClassLoader classLoader)
			throws Exception {
		//设置上下文类加载器为自定义的URLClassLoader
		Thread.currentThread().setContextClassLoader(classLoader);
		//创建主方法运行器 赋完值调用run方法
		createMainMethodRunner(mainClass, args, classLoader).run();
	}

createMainMethodRunner

	protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args,
			ClassLoader classLoader) {
		return new MainMethodRunner(mainClass, args);
	}

**MainMethodRunner **

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对象
		Class<?> mainClass = Thread.currentThread().getContextClassLoader()
				.loadClass(this.mainClassName);
		//反射获取springboot项目中的main方法	(sp[) mainClass就是springboot启动类	
		Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
		//将数组对象赋值给其mian方法 此时springboot项目开始启动执行
		//为什么可以传null 因为一个类的静态方法不属于其所在类只属于其Class类对象
		mainMethod.invoke(null, new Object[] { this.args });//静态方法可以传null
	}

}

扩展
如上可知springboot的启动流程中 启动类的名字不需要非得是main,起名为main的原因是应为main作为jvm的入口我们可以将其作为独立的程序通过run直接执行(idea右键运行可以直接在程序中运行)
在这里插入图片描述

运行

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        System.out.println(MyApplication.class.getClassLoader());
        SpringApplication.run(MyApplication.class, args);
    }
}

在项目中运行时

系统类加载器
sun.misc.Launcher$AppClassLoader@18b4aac2

使用jar包加载时

自定义类加载器
org.springframework.boot.loader.LaunchedURLClassLoader@439f5b3d
原创文章 39 获赞 6 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_42261668/article/details/102988155