通常一个Spring Boot 单体应用的package方式会是jar包的方式,而且通常情况下只要在console以
java -jar
命令加上JAR包的完整名称就能启动应用,那么这个过程是如何实现的呢,不妨让我们在此一探究竟。
通常情况下,Spring Boot 应用打包成JAR包的方式来运行,通过解压一个空白Spring Boot工程(即,由IDE引导生成且不添加任何额外的starter依赖)package之后的 JAR包,再通过我们tree
命令查看其目录结构如下:
从父目录来看,大致可以分为以下几大目录结构:
- BOOT-INF/classes 目录存放应用编译后的class文件;
- BOOT-INF/lib 目录存放应用依赖的jar包;
- META-INF/ 目录存放应用相关的元信息,比如MANIFESTR.MF
- org/ 目录存放Spring Boot相关的class文件
其中,META-INF
目录底下的MANIFESTR.MF
文件描述了整个JAR包的一些元信息。打开 MANIFEST.MF
文件,看看到底提供了一些什么信息。
1 | Manifest-Version: 1.0 |
这么多项信息,其中值得重点关注的是 Main-Class
和 Start-Class
这两项。
Main-Class
属性定义了 JAR 文件的入口类,该类必须是一个可执行的类,一旦定义了该属性即可通过java -jar xxx.jar
来运行该 JAR 文件。
Start-Class
属性定义了整个Spring Boot 应用的程序入口类, 这表明其是一个带有 main() 方法的类。
因为我们的打包方式选择的是 JAR,因此自然而然地,要深入到对应的Launcher 类 JarLauncher.class
中去看看风景。
在项目的pom.xml文件中添加以下坐标,方便我们查看 JarLauncher.class
的源码。
1 | <dependency> |
由它的类结构图可以看到,JarLauncher
类 继承了 ExecutableArchiveLauncher
,而ExecutableArchiveLauncher
又继承了Launcher
类,后两者都是抽象类。在 JarLauncher
中,看到它的 main() 方法如下:
1 | public static void main(String[] args) throws Exception { |
继续看 JarLauncher
的 launch(args)
方法,这个方法的实现放在了抽象基类 Launcher
里边。
1 | protected void launch(String[] args) throws Exception { |
一共三步:
注册jar协议处理器,实际上是注册了
java.protocol.handler.pkgs
这样一个属性, 以便定位到一个确定的URLStreamHandler
去处理 jar URLs . 如何定位到一个具体的URLStreamHandler
, 则是根据具体的 protocol 去 URL::getURLStreamHandler(String protocol) 方法中搜寻了。针对 JAR 协议,调用URLStreamHandler.openConnection()
方法会拿到一个JarURLConnection
,通过这个类来连接上JAR文件后,便可以获取JAR文件里头的信息。 注册完成之后,会清除掉以前设置过的 URLStreamHandler.java1
2
3
4
5
6
7
8
9
10
11
12
13/**
* Reset any cached handlers just in case a jar protocol has already been used. We
* reset the handler by trying to set a null {@link URLStreamHandlerFactory} which
* should have no effect other than clearing the handlers cache.
*/
private static void resetCachedUrlHandlers() {
try {
URL.setURLStreamHandlerFactory(null);
}
catch (Error ex) {
// Ignore
}
}创建类加载器。
java1
2
3
4
5
6
7
8
9
10protected 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]));
}获得所有 archives 的 URL, 并为 所有的 URL 创建类加载器。这里的 Archive 集合主要存放的是 JAR 解压过后的位于
BOOT-INF/classes/
和BOOT-INF/lib/
目录底下的class文件。取得 LauncherClass 名称, 并执行 launch() 方法。注意到这里首先会通过
System.getProperty("jarmode")
尝试获取jarmode
该属性,缺省条件下该值为 null. 因此 LauncherClass 名称由 getMainClass() 方法取得。最后执行
launch(args, launchClass, classLoader);
方法。相关源码如下:java1
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----Launcher.class----
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);
}
----MainMethodRunner.class---
private final String mainClassName;
private final String[] args;
public MainMethodRunner(String mainClass, String[] args) {
this.mainClassName = mainClass;
this.args = (args != null) ? args.clone() : null;
}
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 });
}
可以看到,MainMethodRunner
对象关联 mainClass 及 main 方法参数 args,并最终通过反射的方式,执行mainClass中的main()方法来启动整个应用的。 mainClass 属性从何而来呢?上面说了,由getMainClass() 方法获取。具体源码如下:
1 | private static final String START_CLASS_ATTRIBUTE = "Start-Class"; |
可以看到,实际上, 是读取了 MANIFEST.MF 文件中的 Start-Class
属性的值,并将其赋给了 mainClass. 也就是说, 在这个示例中, mainClass 对应到的是com.example.demo.DemoApplication
, 也就是整个Spring Boot 应用的入口类。
综上,我们一步步地看到了Spring Boot 应用由 java -jar
命令开始,是怎样一步步地找到应用的入口类,进而从入口类的main()方法开始执行,从而成功启动整个应用的。