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
命令来进行断点调试。该命令的具体的使用方法请自行搜索。