前言
通过java -jar xxx.jar
这个启动java程序的套路是Jar包本来就有的特性,sb只是利用了这个强大的功能简化了应用的启动。
MANIFEST.MF
MANIFEST.MF在jar包的META-INF文件夹下,通过jar包启动应用的配置就在这个文件里面。下面看下我的一个sb应用的该文件内容
Manifest-Version: 1.0
Implementation-Title: demo
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.example.demo.DemoApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.1.5.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher
MANIFEST.MF中配置分成两部分,自定义属性和非自定义属性。
非自定义属性就是MANIFEST.MF这个文件中规定的一些特定属性,上面的Manifest-Version,Implementation-Title,Implementation-Version,Created-By,Main-Class都是
而自定义属性,就是自己随便取名的属性,比如Start-Class,Spring-Boot-Classes,Spring-Boot-Lib
其中非自定义属性Main-Class十分重要,java -jar xxx.jar
就是从这个属性对应class的main作为入口启动应用的。
但是我们使用maven插件打包的时候配置的main-class其实对应的Start-Class这个配置,为什么要这样呢?因为一个应用启动还需要用类加载器加载一些其他资源比如jar包以及应用自己的class类,所以Main-Class就做了这些事情。
sb jar包内容一览
--META-INF
----MANIFEST.MF
----maven
------com.example
--------demo
----------pom.xml
----------pom.properties
--listfile.py
--test.jar
--BOOT-INF
----classes
------com
--------example
----------demo
------------DemoApplication.class
------application.properties
----lib
------tomcat-embed-websocket-9.0.19.jar
------jackson-databind-2.9.8.jar
------jackson-core-2.9.8.jar
...
--org
----springframework
------boot
--------loader
----------archive
----------JarLauncher.class
...
通过java -cvf xx.jar
解压jar包后会得到以上格式的文件目录,分为三个文件夹
文件夹 | 作用 |
---|---|
META-INF | 一些配置文件,MANIFEST.MF就在这里 |
BOOT-INF | 子文件class存储应用类,lib存储第三方jar包 |
org | main-class对应的JarLauncher在这里,这边的类用于sb应用启动 |
一些源码
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);
}
protected boolean isNestedArchive(Entry entry) {
return entry.isDirectory() ? entry.getName().equals("BOOT-INF/classes/") : entry.getName().startsWith("BOOT-INF/lib/");
}
public static void main(String[] args) throws Exception {
(new JarLauncher()).launch(args);
}
}
jar包通过JarLauncher的main
方法启动,我们看到这里就一行代码
(new JarLauncher()).launch(args);
这行代码对应2个逻辑
- 初始化包换三方jar包和当前应用的class文件的类加载器
- 启动我们配置的SpringApplication
new JarLauncher()负责加载我们当前这个jar包,launch负责执行以上两个逻辑。
JarLauncher默认构造函数实现为空,它父类ExecutableArchiveLauncher会调用再上一级父类Launcher的createArchive方法加载jar包
public JarLauncher() {
}
public ExecutableArchiveLauncher() {
try {
this.archive = this.createArchive();
} catch (Exception var2) {
throw new IllegalStateException(var2);
}
}
protected final Archive createArchive() throws Exception {
ProtectionDomain protectionDomain = this.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");
} else {
File root = new File(path);
if (!root.exists()) {
throw new IllegalStateException("Unable to determine code source archive from " + root);
} else {
return (Archive)(root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
}
}
}
加载了jar包之后,我们就能获得它里面所有的资源,也就是上一节列出的那些文件。
launch的实现在父类Launcher中
//Launcher
protected void launch(String[] args) throws Exception {
JarFile.registerUrlProtocolHandler();
ClassLoader classLoader = this.createClassLoader(this.getClassPathArchives());
this.launch(args, this.getMainClass(), classLoader);
}
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
this.createMainMethodRunner(mainClass, args, classLoader).run();
}
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
return new MainMethodRunner(mainClass, args);
}
//MainMethodRunner
public void run() throws Exception {
Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.invoke((Object)null, this.args);
}
不多做解释,上面的代码很清晰的展示了launch方法的逻辑,通过createClassLoader构造类加载器,通过this.createMainMethodRunner(mainClass, args, classLoader).run()启动我们的sb应用。