SpringBoot直接启动与jar包启动的不同

SpringBoot作为Java后台开发常用的技术点之一,相信很多人不陌生,但是其中的一些有趣的点,很多人并不一定都了解。

首先,在springbbot程序主函数处,加上一句控制台打印消息,打印出加载当前应用的入口函数所在类的加载器信息。

直接启动

在开发工具中,直接启动该应用,观察到如下所示: 对类加载器有所了解的应该知道,输出的含义为:系统类/应用类加载器

Jar包的方式启动

通过maven或者gradle等方式对程序进行打包,生成可运行的Jar包,然后通过java -jar xx.jar的方式启动,观察到如下所示: 通过Jar包的方式运行的加载器为:LaunchedURLClassLoader,造成两者不同的原因是什么呢?

Jar包的结构

可执行Jar也是一种压缩包,使用压缩工具对其进行解压后,会得到如下所示的结构: 在META_INF目录下能看到MANIFEST.MF,该文件是打包时自动生成的,是 清单文件,内容大体如下: 其中Main-Class是Jar包启动的入口文件,找到该文件,然后分析执行流程或者通过断点的方式观察其执行的步骤,可以看到:

// step1(JarLauncher.java)
public static void main(String[] args) throws Exception {
          
   
	new JarLauncher().launch(args); // 跟踪launch()方法
}

// step2(Launcher.java)
protected void launch(String[] args) throws Exception {
          
   
	if (!isExploded()) {
          
   
		JarFile.registerUrlProtocolHandler();
	}
	// 跟踪createClassLoader(),该函数返回的结果就是jar包启动方式的加载器
	ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
	String jarMode = System.getProperty("jarmode");
	String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
	launch(args, launchClass, classLoader);
}

// step3(ExecutableArchiveLauncher.java)
@Override
protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
          
   
	List<URL> urls = new ArrayList<>(guessClassPathSize());
	while (archives.hasNext()) {
          
   
		urls.add(archives.next().getUrl());
	}
	if (this.classPathIndex != null) {
          
   
		urls.addAll(this.classPathIndex.getUrls());
	}
	// 创建类加载器
	return createClassLoader(urls.toArray(new URL[0]));
}

// step4(Launcher.java)
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
          
   
	 // 此处返回类加载器
	return new LaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass().getClassLoader());
}

通过如上的分析,可以知道加载Jar包的类加载器,以及其基本的加载流程。而直接启动的方式会直接运行入口函数,main函数在类路径下,因此直接启动的方式是系统类加载器加载的。

想要更加深入的了解Jar包的执行流程,可以使用java -agentlib:jdwp命令来进行断点调试。该命令的具体的使用方法请自行搜索。

经验分享 程序员 微信小程序 职场和发展