天天看点

java -jar命令启动SpringBoot应用原理分析

作者:授道者

一、概述

使用过SprongBoot打过jar包的都应该知道,目标文件一般都会生成两个文件,一个是以.jar的包,一个是.jar.original文件。那么使用SpringBoot会打出两个包,而.jar.original的作用是什么呢?还有就是java -jar是如何将一个SpringBoot项目启动的呢?

其实.jar.original是在SpringBoot重新打包之前的原始jar包,内部只包含了项目的用户类,不包含其他的依赖jar包,生成之后,SpringBoot会重新打包之后,最后生成.jar包,内部包含了原始jar包以及其他的引用依赖。

SpringBoot Maven插件打包配置

<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<version>2.7.5</version>
				<configuration>
           //指定SpringBoot项目的main函数的主类入口
					<mainClass>xxx.xxx. ..server.bootstrap.LayoutServerApplication</mainClass>
         //打成jar包
					<layout>JAR</layout> 
				</configuration>
				<executions>
					<execution>
						<goals>
							<goal>repackage</goal>
						</goals>
					</execution>
				</executions>
			</plugin>           

xxx.jar.original文件

只包含用户项目中编写的类及其资源信息,不包含其他依赖jar包

java -jar命令启动SpringBoot应用原理分析

xxx.jar文件

内部包含了原始jar包以及其他的引用依赖jar包

java -jar命令启动SpringBoot应用原理分析

二、SpringBoot xxx.jar包内部结构解析

  • BOOT-INF:这个文件夹下有两个文件夹classes用来存放用户类,也就是原始jar.original里的类;还有一个是lib,就是这个原始jar.original引用的依赖。
  • META-INF:这里是通过java -jar启动的入口信息,记录了入口类的位置等信息。
  • org:org.springframework.boot.loader包下的字节码文件,通过它来启动应用。

这里主要介绍META-INF/MANIFEST.MF文件

Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Archiver-Version: Plexus Archiver
Built-By: admin
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: xxx.layout.server.bootstrap.LayoutServerApplication //项目main函数的主类入口
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.7.5
Created-By: Apache Maven 3.5.2
Build-Jdk: 1.8.0_261
Main-Class: org.springframework.boot.loader.JarLauncher //jar包启动类           

三、启动过程解析

从MANIFEST.MF文件中的Main-class指向入口开始。

创建JarLauncher并且通过它的launch()方法开始加载jar包内部信息。

//org.springframework.boot.loader.JarLauncher的main()方法
public static void main(String[] args) throws Exception {
        (new JarLauncher()).launch(args);
 }           

JarLauncher的父类ExecutableArchiveLauncher会去加载jar中的所有文件

public ExecutableArchiveLauncher() {
        try {
            this.archive = this.createArchive(); //加载归档文件
            this.classPathIndex = this.getClassPathIndex(this.archive);
        } catch (Exception var2) {
            throw new IllegalStateException(var2);
        }
    }           

获取项目中指定的main函数主类

protected void launch(String[] args) throws Exception {
        if (!this.isExploded()) {
            JarFile.registerUrlProtocolHandler();
        }

        ClassLoader classLoader = this.createClassLoader(this.getClassPathArchivesIterator());
        String jarMode = System.getProperty("jarmode");
       //this.getMainClass()获取主类方法
        String launchClass = jarMode != null && !jarMode.isEmpty() ? "org.springframework.boot.loader.jarmode.JarModeLauncher" : this.getMainClass();
        this.launch(args, launchClass, classLoader);
    }

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("Start-Class");
        }

        if (mainClass == null) {
            throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
        } else {
            return mainClass;
        }
    }           

通过反射调用项目中指定启动主类的main方法

protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {
        Thread.currentThread().setContextClassLoader(classLoader);
        this.createMainMethodRunner(launchClass, args, classLoader).run();
    }

public void run() throws Exception {
        Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
        Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
        mainMethod.setAccessible(true);
       //反射调用main方法
        mainMethod.invoke((Object)null, this.args);
    }

//项目中的主类main方法被调用,SpringBoot应用启动
@SpringBootApplication
public class LayoutServerApplication
{
    private static final Logger logger = LoggerFactory.getLogger(LayoutServerApplication.class);

    public static void main(String[] args)
    {
        SpringApplication.run(LayoutServerApplication.class, args);
        logger.info("-------layout-server启动完成-----");
    }
}