一、java 命令启动 Spring Boot 应用

spring boot 应用通过打包成 jar 包之后可以通过 java -jar xxx.jar 的方式直接启动应用。

将 spring boot 应用打包成 jar 包需要如下几个步骤

1.1、pom 增加 maven 插件

  1. <build>
  2. <plugins>
  3. <plugin>
  4. <groupId>org.springframework.boot</groupId>
  5. <artifactId>spring-boot-maven-plugin</artifactId>
  6. </plugin>
  7. </plugins>
  8. </build>

1.2、maven 命令打包

  1. mvn clean -Dmaven.test.skip=true install

target 目录下生成jar文件 zuul-0.0.1-SNAPSHOT.jar

1.3、java -jar 命令启动服务

  1. java -jar zuul-0.0.1-SNAPSHOT.jar

二、jar 包文件分析

jar 包文件结构

jar -xvf xxx.jar 解压 jar 文件

01.png

各文件目录存放内容

  • 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 相关内容如下

官方文档说明

参考博客

  1. ## 用来定义 manifest文件 的版本
  2. Manifest-Version: 1.0
  3. Implementation-Title: zuul
  4. Implementation-Version: 0.0.1-SNAPSHOT
  5. Start-Class: com.qguofeng.server.ZuulApplication
  6. Spring-Boot-Classes: BOOT-INF/classes/
  7. Spring-Boot-Lib: BOOT-INF/lib/
  8. Build-Jdk-Spec: 1.8
  9. Spring-Boot-Version: 2.1.5.RELEASE
  10. ## 该文件的生成者,一般该属性是由jar命令行工具生成的
  11. Created-By: Maven Archiver 3.4.0
  12. ## main函数所在的全限定类名,该类必须是一个可执行的类,可以狭义理解为存在 main()函数的类
  13. 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

Spring Boot Loader 相关类图结构如下
02.png

分析 JarLauncher#main

JarLauncher#main 代码执行时序图
03.png
② Launcher#launch 相关执行代码

  1. public abstract class Launcher {
  2. protected void launch(String[] args) throws Exception {
  3. JarFile.registerUrlProtocolHandler();
  4. ClassLoader classLoader = this.createClassLoader(this.getClassPathArchives());
  5. this.launch((String[])args, (String)this.getMainClass(), (ClassLoader)classLoader);
  6. }
  7. // 重载方法
  8. protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
  9. Thread.currentThread().setContextClassLoader((ClassLoader)classLoader);
  10. // 调用 MainMethodRunner#run
  11. this.createMainMethodRunner((String)mainClass, (String[])args, (ClassLoader)classLoader).run();
  12. }
  13. // 构建对象 MainMethodRunner
  14. protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
  15. return new MainMethodRunner((String)mainClass, (String[])args);
  16. }
  17. }

② Launcher#launch 最终执行调用的 MainMethodRunner#run ,而其中一个参数 mainClass 值得关注。

mainClass 参数的获取在 ExecutableArchiveLauncher#getMainClass 方法中,相关代码如下

  1. public abstract class ExecutableArchiveLauncher extends Launcher {
  2. @Override
  3. protected String getMainClass() throws Exception {
  4. Manifest manifest = this.archive.getManifest();
  5. String mainClass = null;
  6. if (manifest != null) {
  7. // 对应 META-INF/MANIFEST.MF 文件中的 Start-Class 属性
  8. mainClass = manifest.getMainAttributes().getValue((String)"Start-Class");
  9. }
  10. if (mainClass == null) {
  11. throw new IllegalStateException((String)new StringBuilder().append((String)"No 'Start-Class' manifest entry specified in ").append((Object)this).toString());
  12. }
  13. return mainClass;
  14. }
  15. }

分析 MainMethodRunner#run

从上面流程能够知道传入 MainMethodRunner 中的 mainclass 参数为 META-INF/MANIFEST.MF 中的 Start-Class 属性,而 Start-Class 对应的为我们定义的springboot启动类 ZuulApplication.class

来看看 MainMethodRunner#run 中的执行逻辑

  1. public class MainMethodRunner {
  2. private final String mainClassName;
  3. private final String[] args;
  4. public MainMethodRunner(String mainClass, String[] args) {
  5. this.mainClassName = mainClass;
  6. this.args = args != null ? (String[])args.clone() : null;
  7. }
  8. public void run() throws Exception {
  9. // 加载 Application.class 类,并执行 其 main 方法
  10. Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass((String)this.mainClassName);
  11. Method mainMethod = mainClass.getDeclaredMethod((String)"main", String[].class);
  12. mainMethod.invoke(null, (Object[])new Object[]{this.args});
  13. }
  14. }

上述代码很清晰,直接加载 ZuulApplication.class ,然后通过反射调用其 main 方法

来看一下 ZuulApplication 的代码

  1. @SpringBootApplication
  2. @EnableZuulProxy
  3. public class ZuulApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(ZuulApplication.class, args);
  6. }
  7. }

就是 spring boot 项目中的启动类。

三、java -jar 直接启动spring boot 应用原因

在 jar 包中的 META-INF/MANIFEST.MF 中存在两个属性

  1. ## Spring Boot 启动类
  2. Start-Class: com.qguofeng.server.ZuulApplication
  3. ## main函数所在的全限定类名,该类必须是一个可执行的类,可以狭义理解为存在 main()函数的类
  4. Main-Class: org.springframework.boot.loader.JarLauncher

java -jar xxx.jar 命令会执行 属性 Main-Class 配置的类 JarLauncher 的 main 方法。

JarLauncher 会通过反射的方式执行 属性 Start-Class 配置的类 ZuulApplication 的 main 方法。

相当于直接运行 ZuulApplication#main

run方法直接启动应用