一、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.0Implementation-Title: zuulImplementation-Version: 0.0.1-SNAPSHOTStart-Class: com.qguofeng.server.ZuulApplicationSpring-Boot-Classes: BOOT-INF/classes/Spring-Boot-Lib: BOOT-INF/lib/Build-Jdk-Spec: 1.8Spring-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#runthis.createMainMethodRunner((String)mainClass, (String[])args, (ClassLoader)classLoader).run();}// 构建对象 MainMethodRunnerprotected 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 {@Overrideprotected 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@EnableZuulProxypublic 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
