一、java 命令启动 Spring Boot 应用
spring boot 应用通过打包成 jar 包之后可以通过 java -jar xxx.jar
的方式直接启动应用。
将 spring boot 应用打包成 jar 包需要如下几个步骤
1.1、pom 增加 maven 插件
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
1.2、maven 命令打包
mvn clean -Dmaven.test.skip=true install
在 target
目录下生成jar文件 zuul-0.0.1-SNAPSHOT.jar
1.3、java -jar
命令启动服务
java -jar zuul-0.0.1-SNAPSHOT.jar
二、jar 包文件分析
jar 包文件结构
jar -xvf xxx.jar
解压 jar 文件
各文件目录存放内容
BOOT-INF/classes/
:目录-自己模块代码BOOT-INF/lib/
:目录-放置第三方依赖的 jar包META-INF/maven/
:目录-存放 pom 文件META-INF/MANIFEST.MF
:文件-描述 jar包信息org/springframework/boot/loader/
:目录-spring boot loader 相关代码
jar 包描述信息 META-INF/MANIFEST.MF
相关内容如下
## 用来定义 manifest文件 的版本
Manifest-Version: 1.0
Implementation-Title: zuul
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.qguofeng.server.ZuulApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.1.5.RELEASE
## 该文件的生成者,一般该属性是由jar命令行工具生成的
Created-By: Maven Archiver 3.4.0
## main函数所在的全限定类名,该类必须是一个可执行的类,可以狭义理解为存在 main()函数的类
Main-Class: org.springframework.boot.loader.JarLauncher
通过 META-INF/MANIFEST.MF
中的 Main-Class
能够知道 java -jar xxx.jar
命令启动运行的是 org.springframework.boot.loader.JarLauncher
main
函数。
Spring Boot Loader
分析 JarLauncher#main
JarLauncher#main
代码执行时序图② Launcher#launch
相关执行代码
public abstract class Launcher {
protected void launch(String[] args) throws Exception {
JarFile.registerUrlProtocolHandler();
ClassLoader classLoader = this.createClassLoader(this.getClassPathArchives());
this.launch((String[])args, (String)this.getMainClass(), (ClassLoader)classLoader);
}
// 重载方法
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
Thread.currentThread().setContextClassLoader((ClassLoader)classLoader);
// 调用 MainMethodRunner#run
this.createMainMethodRunner((String)mainClass, (String[])args, (ClassLoader)classLoader).run();
}
// 构建对象 MainMethodRunner
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
return new MainMethodRunner((String)mainClass, (String[])args);
}
}
② Launcher#launch
最终执行调用的 MainMethodRunner#run
,而其中一个参数 mainClass
值得关注。
mainClass
参数的获取在 ExecutableArchiveLauncher#getMainClass
方法中,相关代码如下
public abstract class ExecutableArchiveLauncher extends Launcher {
@Override
protected String getMainClass() throws Exception {
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
// 对应 META-INF/MANIFEST.MF 文件中的 Start-Class 属性
mainClass = manifest.getMainAttributes().getValue((String)"Start-Class");
}
if (mainClass == null) {
throw new IllegalStateException((String)new StringBuilder().append((String)"No 'Start-Class' manifest entry specified in ").append((Object)this).toString());
}
return mainClass;
}
}
分析 MainMethodRunner#run
从上面流程能够知道传入 MainMethodRunner
中的 mainclass 参数为 META-INF/MANIFEST.MF
中的 Start-Class
属性,而 Start-Class
对应的为我们定义的springboot启动类 ZuulApplication.class
。
来看看 MainMethodRunner#run
中的执行逻辑
public class MainMethodRunner {
private final String mainClassName;
private final String[] args;
public MainMethodRunner(String mainClass, String[] args) {
this.mainClassName = mainClass;
this.args = args != null ? (String[])args.clone() : null;
}
public void run() throws Exception {
// 加载 Application.class 类,并执行 其 main 方法
Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass((String)this.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod((String)"main", String[].class);
mainMethod.invoke(null, (Object[])new Object[]{this.args});
}
}
上述代码很清晰,直接加载 ZuulApplication.class
,然后通过反射调用其 main
方法
来看一下 ZuulApplication
的代码
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
三、java -jar 直接启动spring boot 应用原因
在 jar 包中的 META-INF/MANIFEST.MF
中存在两个属性
## Spring Boot 启动类
Start-Class: com.qguofeng.server.ZuulApplication
## main函数所在的全限定类名,该类必须是一个可执行的类,可以狭义理解为存在 main()函数的类
Main-Class: org.springframework.boot.loader.JarLauncher
java -jar xxx.jar
命令会执行 属性 Main-Class
配置的类 JarLauncher
的 main 方法。
而 JarLauncher
会通过反射的方式执行 属性 Start-Class
配置的类 ZuulApplication
的 main 方法。
相当于直接运行 ZuulApplication#main