SpringBoot 程序是如何通过java -jar
启动的
SpringBoot打包方式主要有 jar 和 war 两种,以下仅针对 jar 进行分析
MANIFEST.MF
当我们将程序打成 Jar,我们总会发现在 META-INF
文件夹中有一个 MANIFEST.MF
文件,该文件包含了该 Jar 包的主要信息。如果算可执行的 Jar 包,该文件中还会包含一个 Main-Class
属性,表明方法入口。以下是一个 SpringBoot 程序的 MANIFEST.MF
内容
1 2 3 4 5 6 7 8 9 10 11 12
| Manifest-Version: 1.0 Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx Implementation-Title: demo Implementation-Version: 0.0.1-SNAPSHOT Start-Class: com.yxd.es.DemoApplication Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Build-Jdk-Spec: 1.8 Spring-Boot-Version: 2.3.1.RELEASE Created-By: Maven Jar Plugin 3.2.0 Implementation-Vendor: Pivotal Software, Inc. Main-Class: org.springframework.boot.loader.JarLauncher
|
Archive
- archive即归档文件,这个概念在linux下比较常见
- 通常就是一个tar/zip格式的压缩包
- jar是zip格式
在spring boot里,抽象出了Archive的概念。
一个archive可以是一个jar(JarFileArchive),也可以是一个文件目录(ExplodedArchive)。可以理解为Spring boot抽象出来的统一访问资源的层。
上面的demo-0.0.1-SNAPSHOT.jar 是一个Archive,然后demo-0.0.1-SNAPSHOT.jar里的/lib目录下面的每一个Jar包,也是一个Archive。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public interface Archive extends Iterable<Archive.Entry>, AutoCloseable { URL getUrl(); Manifest getManifest(); Iterator<Archive> getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter); boolean isExploded() interface Entry { boolean isDirectory(); String getName(); }
@FunctionalInterface interface EntryFilter { boolean matches(Entry entry); } }
|
Launcher
值得注意的是,上面的 MANIFEST
文件中的 Main-Class
属性并不是我们 SpringBoot 程序中的启动类,而 Start-Class
属性才是。
在打成Jar包的时候,SpringBoot 插件会在org/springframework/boot/loader
目录中载入以下几个文件
其中就有 Main-Class
属性中的 JarLauncher
类,也就是说 java -jar
之后会调用 JarLauncher#main
来启动程序。类似的,如果我们打成的是 war 包,则会调用 WarLauncher#main
主要类继承关系如下:
主要流程如下:
1 2 3 4 5 6
| public class JarLauncher extends ExecutableArchiveLauncher { ... public static void main(String[] args) throws Exception { new JarLauncher().launch(args); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public abstract class Launcher { ... protected void launch(String[] args) throws Exception { if (!isExploded()) { JarFile.registerUrlProtocolHandler(); } ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator()); String jarMode = System.getProperty("jarmode"); String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass(); launch(args, launchClass, classLoader); }
protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception { Thread.currentThread().setContextClassLoader(classLoader); createMainMethodRunner(launchClass, args, classLoader).run(); } protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) { return new MainMethodRunner(mainClass, args); } ... }
|
上述过程中有一个获取类加载器的方法,大致流程如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public abstract class ExecutableArchiveLauncher extends Launcher { ... @Override protected Iterator<Archive> getClassPathArchivesIterator() throws Exception { Archive.EntryFilter searchFilter = this::isSearchCandidate; Iterator<Archive> archives = this.archive.getNestedArchives(searchFilter, (entry) -> isNestedArchive(entry) && !isEntryIndexed(entry)); if (isPostProcessingClassPathArchives()) { archives = applyClassPathArchivePostProcessing(archives); } return archives; } ... }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public abstract class Launcher { ...
protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception { List<URL> urls = new ArrayList<>(50); while (archives.hasNext()) { Archive archive = archives.next(); urls.add(archive.getUrl()); archive.close(); } return createClassLoader(urls.toArray(new URL[0])); } ... }
|
启动程序调用类
1 2 3 4 5 6 7 8 9 10 11
| public class MainMethodRunner { ... public void run() throws Exception { Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader()); Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); mainMethod.setAccessible(true); mainMethod.invoke(null, new Object[] { this.args }); } ... }
|
之后便是执行SpringApplication#run
方法启动真正的程序,然后就是加载配置、加载Bean等等。