Java包可以通过jar和war启动。看看如何启动的,细细道来
java -jar xxx.jar
SpringBoot使用maven打package,会有两个文件
由于jar是采用zip压缩格式进行存储的
linux使用unzip对xxx.jar进行解压
unzip -x -q xxx.jar -d xxx
解压后
- BOOT-INF/class 目录存储应用编译后的class文件
- BOOT-INF/lib 目录存放的是应用依赖的jar包
- org 目录存储SPringBoot相关的class文件
其中有一个MANIFEST.MF文件
关注两个东西
- Start-Class: Springboot的启动类
- Main-Class: JarLauncher是SPringBoot的启动类
JarLauncher是如何实现启动main(String args)的呢
在开始之前需要引用核心包
<!--spring-boot-loader-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<scope>provided</scope>
</dependency>
首先看下类图图
我们可以发现 Launcher 有一个抽象类 ExecutableArchiveLauncher ,JarLauncher 和 WarLauncher 都继承了 ExecutableArchiveLauncher
进入JarLauncher类中,执行main方法
public class JarLauncher extends ExecutableArchiveLauncher {
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
}
launch() 方法是 抽象类 Launcher 实现的方法
public abstract class Launcher {
...
protected void launch(String[] args) throws Exception {
JarFile.registerUrlProtocolHandler();
ClassLoader classLoader = createClassLoader(getClassPathArchives());
launch(args, getMainClass(), classLoader);
}
}
launch方法氛围三步骤
- 注册URL并清除缓存
- 设置类价值路径
- 执行main方法
JarFile.registerUrlProtocolHandler();
URL#getURLStreamHandler(String) 提供。该方法利用了java.net.URLStreamHandler扩展机制,其实现由
public class JarFile extends java.util.jar.JarFile {
...
// 注册URL协议
public static void registerUrlProtocolHandler() {
String handlers = System.getProperty(PROTOCOL_HANDLER, "");
System.setProperty(PROTOCOL_HANDLER, ("".equals(handlers) ? HANDLERS_PACKAGE
: handlers + "|" + HANDLERS_PACKAGE));
resetCachedUrlHandlers();
}
// 重置并清除应用缓存
private static void resetCachedUrlHandlers() {
try {
URL.setURLStreamHandlerFactory(null);
}
catch (Error ex) {
// Ignore
}
}
}
createClassLoader ( getClassPathArchivesIterator ())
其中创建Launcher#createClassLoader(java.net.URL[]) 由子类实现
sNestedArchive(Archive.Entry entry) 需要子类 JarLauncher 或 WarLauncher 实现,以 JarLauncher 为例
public class JarLauncher extends ExecutableArchiveLauncher {
...
@Override
protected boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
}
}
该方法主要是为了过滤掉 Archive.Entry 实例是否匹配 BOOT-INF/lib 或 BOOT-INF/classes ,只要符合以上路径即可,故 getClassPathArchives() 的返回值还是取决于archives 属性对象的内容。
public abstract class ExecutableArchiveLauncher extends Launcher {
private final Archive archive;
public ExecutableArchiveLauncher() {
try {
this.archive = createArchive();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
...
}
createArchive() 该方法来自于父类 Launcher ,该方法主要判断文件路径和归档文件是否正确
public abstract class Launcher {
...
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));
}
}
launch(args, getMainClass(), classLoader)
该方法的实际执行者是 createMainMethodRunner()
public abstract class Launcher {
...
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(mainClass, args, classLoader).run();
}
...
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args,
ClassLoader classLoader) {
return new MainMethodRunner(mainClass, args);
}
}
MainMethodRunner 对象关联 mainClass 及 main 方法参数 args
public class MainMethodRunner {
...
public MainMethodRunner(String mainClass, String[] args) {
this.mainClassName = mainClass;
this.args = (args != null ? args.clone() : null);
}
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 });
}
}
而 mainClass 来自 getMainClass()
public abstract class ExecutableArchiveLauncher extends Launcher {
...
@Override
protected String getMainClass() throws Exception {
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if (mainClass == null) {
throw new IllegalStateException(
"No 'Start-Class' manifest entry specified in " + this);
}
return mainClass;
}
}
类名称来自于 /META-INF/MANIFEST.MF 资源中的 Start-Class 属性,其子类没有覆盖此方法实现,故无论是JAR还是WAR,读取 Spring Boot 启动类均来自于此属性。获取mainClass 之后会执MainMethodRunner#run()方法去读取mainClass 类中的main(String[] args)方法
// 方法一开始就展示了,为了更加清楚哪里调用,再写一遍
public abstract class Launcher {
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(mainClass, args, classLoader).run();
}
}
参考:https://blog.csdn.net/qq_38423105/article/details/89433050