# java 命令启动 Spring Boot 应用

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

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

# pom 增加 maven 插件

<build>
	<plugins>
		<plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
	</plugins>
</build>

# maven 命令打包

mvn clean -Dmaven.test.skip=true install

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

# 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

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);
    }
}

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

# 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

run方法直接启动应用

精彩内容推送,请关注公众号!
最近更新时间: 4/28/2020, 12:29:58 PM