spring-boot-loader的启动与加载全流程

spring-boot-loader的启动与加载全流程

spring boot 应用可执行JAR资源结构

利用命令gradle bootJar 打包 得到 spring_lecture-1.0-SNAPSHOT.jar

JAR文件有时也称为“Fat JAR”,采用zip压缩格式存储,因此凡是能解压zip压缩文件的软件,均可将JAR包解压。

解压

1
uzip spring_lecture-1.0-SNAPSHOT.jar -d ./spring_lecture

目录结构:

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
29
30
.
├── BOOT-INF
│   ├── classes
│   │   ├── application.yml
│   │   └── com
│   │   └── bob
│   │   └── boot
│   │   ├── MyApplication.class
│   │   ├── config
│   │   │   ├── MyConfig.class
│   │   │   └── MyConfigBean.class
│   │   ├── controller
│   │   │   └── MyController.class
│   │   └── domain
│   │   └── Person.class
│   └── lib
│   ├── ...
│   ├── spring-beans-5.1.5.RELEASE.jar
│   ├── spring-boot-2.1.3.RELEASE.jar
│   ├── spring-boot-autoconfigure-2.1.3.RELEASE.jar
│   └── ...
├── META-INF
│   └── MANIFEST.MF
└── org
└── springframework
└── boot
└── loader
├── ExecutableArchiveLauncher.class
├── JarLauncher.class
├── ...

  • BOOT-INF/classes 目录存放应用编译后的class文件
  • BOOT-INF/lib 目录存放应用依赖的JAR包
  • META-INF/ 目录存放应用相关的元信息,如MANIFEST.MF文件
  • org/ 目录存放Spring Boot相关的class文件。

FAT JAR和WAR执行模块 spring-boot-loader

启动命令:

1
java -jar spring_lecture-1.0-SNAPSHOT.jar

Java官方文档规定,java -jar 命令引导的具体启动类必须配置在 MANIFEST.MF 资源的 Main-Class 属性中。

JAR文件规范,MANIFEST.MF 资源必须存放在 /META-INF/目录下

入口 MANIFEST.MF

1
2
3
Manifest-Version: 1.0
Start-Class: com.bob.boot.MyApplication
Main-Class: org.springframework.boot.loader.JarLauncher

可执行JAR文件的启动器JarLauncher

项目中是找不到这个类的,因为在构建的时候插件把这些内容打进了jar包里,但是项目里是没有引用的,接下来我们就要在项目中引入这些jar包。

build.gradle引入spring-boot-loader

1
2
3
4
5
6
7
dependencies {
compile (
'org.springframework.boot:spring-boot-starter-web',
'org.springframework.boot:spring-boot-starter-test',
'org.springframework.boot:spring-boot-loader'
)
}

JarLauncher的实现原理

JarLauncher

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
29
30
31
32
33
34
/**
* {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are
* included inside a {@code /BOOT-INF/lib} directory and that application classes are
* included inside a {@code /BOOT-INF/classes} directory.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
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()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
}
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
}

在一个jar文件中我们必须要将被指定为Main-Class的类直接放置到jar文件内部的顶层目录,不允许再被嵌套。
jar文件的嵌套,在jar的规范中是不允许的,这样会导致目标类是无法被执行的。所以是将spring-boot-loader jar包中的class类拷贝到了打包后jar文件的顶层目录

启动时执行main方法,调用JarLauncher构造方法时,首先调用父类ExecutableArchiveLauncher构造方法,再调用父类Launcher里的createArchive,这里会根据jar包的全路径返回一个Archive对象赋值给final属性archive

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* Base class for executable archive {@link Launcher}s.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
public abstract class ExecutableArchiveLauncher extends Launcher {
private final Archive archive;
public ExecutableArchiveLauncher() {
try {
this.archive = createArchive();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
...
}
/**
* Base class for launchers that can start an application with a fully configured
* classpath backed by one or more {@link Archive}s.
*
* @author Phillip Webb
* @author Dave Syer
*/
public abstract class Launcher {
...
//根据jar包的全路径返回一个Archive对象
protected final Archive createArchive() throws Exception {
ProtectionDomain protectionDomain = getClass().getProtectionDomain();
CodeSource codeSource = protectionDomain.getCodeSource();
URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
String path = (location != null) ? location.getSchemeSpecificPart() : null;
if (path == null) {
throw new IllegalStateException("Unable to determine code source archive");
}
File root = new File(path);
if (!root.exists()) {
throw new IllegalStateException(
"Unable to determine code source archive from " + root);
}
return (root.isDirectory() ? new ExplodedArchive(root)
: new JarFileArchive(root));
}
}

然后调用JarLauncher实例的launch方法

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/**
* Base class for launchers that can start an application with a fully configured
* classpath backed by one or more {@link Archive}s.
*
* @author Phillip Webb
* @author Dave Syer
*/
public abstract class Launcher {
/**
* Launch the application. This method is the initial entry point that should be
* called by a subclass {@code public static void main(String[] args)} method.
* @param args the incoming arguments
* @throws Exception if the application fails to launch
*/
protected void launch(String[] args) throws Exception {
JarFile.registerUrlProtocolHandler();
//根据指定的归档文件创建一个类加载器
ClassLoader classLoader = createClassLoader(getClassPathArchives());
launch(args, getMainClass(), classLoader);
}
/**
* Create a classloader for the specified archives.
* @param archives the archives
* @return the classloader
* @throws Exception if the classloader cannot be created
*/
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<>(archives.size());
for (Archive archive : archives) {
urls.add(archive.getUrl());
}
return createClassLoader(urls.toArray(new URL[0]));
}
/**
* Create a classloader for the specified URLs.
* @param urls the URLs
* @return the classloader
* @throws Exception if the classloader cannot be created
*/
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}
/**
* Launch the application given the archive file and a fully configured classloader.
* @param args the incoming arguments
* @param mainClass the main class to run
* @param classLoader the classloader
* @throws Exception if the launch fails
*/
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(mainClass, args, classLoader).run();
}
...
}
/**
* Base class for executable archive {@link Launcher}s.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
public abstract class ExecutableArchiveLauncher extends Launcher {
private final Archive archive;
...
/**
* Returns the archives that will be used to construct the class path.
* @return the class path archives
* @throws Exception if the class path archives cannot be obtained
*/
@Override
protected List<Archive> getClassPathArchives() throws Exception {
List<Archive> archives = new ArrayList<>(
this.archive.getNestedArchives(this::isNestedArchive));
postProcessClassPathArchives(archives);
return archives;
}
...
}
/**
* Utility class that is used by {@link Launcher}s to call a main method. The class
* containing the main method is loaded using the thread context class loader.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
public class MainMethodRunner {
...
public void run() throws Exception {
Class<?> mainClass = Thread.currentThread().getContextClassLoader()
.loadClass(this.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.invoke(null, new Object[] { this.args });
}
}