SpringBoot的jar包启动流程梳理

举例:Springboot的可执行demo.jar包

执行命令 java -jar demo.jar时,Java 虚拟机的第一步先扫描 demo.jar文件的内部结构,定位并读取 META-INF/MANIFEST.MF 文件,MAINIFEST.MF文件是demo.jar的说明书。

Springboot的可执行demo.jar包目录结构如下所示

/BOOT-INF/classes : 存放应用的编译类文件

/BOOT-INF/lib:存放所有依赖第三方的JAR文件

/META-INF/MANIFEST.MF: Java 归档文件的一个标准元数据文件,包含该 JAR 包的清单信息

/org.springframework.boot.loader:Springboot启动需要的相关类

执行核心步骤

获取到的MANIFEST.MF文件信息如下

makefile 复制代码
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Archiver-Version: Plexus Archiver
Built-By: test
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.demo.DemoApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.7.18
Created-By: Apache Maven 3.8.2
Build-Jdk: 1.8.0_381
Main-Class: org.springframework.boot.loader.JarLauncher

Main-Class: org.springframework.boot.loader.JarLauncher ,指定了jar包的入口点

Start-Class: com.demo.DemoApplication,指定了应用程序的主类

org.springframework.boot.loader.JarLauncher的继承关系如下所示

java 复制代码
public class JarLauncher extends ExecutableArchiveLauncher {

    static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
       if (entry.isDirectory()) {
          return entry.getName().equals("BOOT-INF/classes/");
       }
       return entry.getName().startsWith("BOOT-INF/lib/");
    };

    public JarLauncher() {
    }

    protected JarLauncher(Archive archive) {
       super(archive);
    }

    @Override
    protected boolean isPostProcessingClassPathArchives() {
       return false;
    }

    @Override
    protected boolean isNestedArchive(Archive.Entry entry) {
       return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
    }

    @Override
    protected String getArchiveEntryPathPrefix() {
       return "BOOT-INF/";
    }

    public static void main(String[] args) throws Exception {
       new JarLauncher().launch(args);
    }

}

执行的是Jarlauncher.main方法,该方法先创建一个JarLauncher对象,继承于抽象类ExecutableArchiveLauncher,抽象类ExecutableArchiveLauncher的构造方法会创建一个Archive对象

java 复制代码
public ExecutableArchiveLauncher() {
    try {
       this.archive = createArchive();
       this.classPathIndex = getClassPathIndex(this.archive);
    }
    catch (Exception ex) {
       throw new IllegalStateException(ex);
    }
}

protected final Archive createArchive() throws Exception {
    ProtectionDomain protectionDomain = getClass().getProtectionDomain();
    CodeSource codeSource = protectionDomain.getCodeSource();
    URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
    String path = (location != null) ? location.getSchemeSpecificPart() : null;
    if (path == null) {
       throw new IllegalStateException("Unable to determine code source archive");
    }
    File root = new File(path);
    if (!root.exists()) {
       throw new IllegalStateException("Unable to determine code source archive from " + root);
    }
    return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
}

创建JarLauncher对象后会拿到整个Jar包的结构信息,包括BOOT-INF/classes、BOOT-INF/lib、META-INF等。之后执行JarLauncher对象的launch方法,实际调用的是父级抽象类的Launcher类中的launch方法

java 复制代码
public abstract class Launcher {
    protected void launch(String[] args) throws Exception {
        if (!isExploded()) {
           JarFile.registerUrlProtocolHandler();
        }
        // 这里创建自己的类加载器进行加载,具体类为LaunchedURLClassLoader
        ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
        String jarMode = System.getProperty("jarmode");
        //getMainClass()方法会从MANIFEST.MF文件中获取Start-Class,
        //执行其 main方法,也就是我们服务的 SpringApplication的main方法 
        String launchClass = (jarMode != null && !jarMode.isEmpty()) ? 
        JAR_MODE_LAUNCHER : getMainClass();
        launch(args, launchClass, classLoader);
    }
    //启动
    protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {
        //// 将创建的类加载器放到线程上下文中
        Thread.currentThread().setContextClassLoader(classLoader);
        //创建main runner执行
        createMainMethodRunner(launchClass, args, classLoader).run();
    }
    protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
        return new MainMethodRunner(mainClass, args);
    }
}
public abstract class ExecutableArchiveLauncher extends Launcher {
    private static final String START_CLASS_ATTRIBUTE = "Start-Class";
    @Override
    protected String getMainClass() throws Exception {
        Manifest manifest = this.archive.getManifest();
        String mainClass = null;
        if (manifest != null) {
           mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE);
        }
        if (mainClass == null) {
           throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
        }
        return mainClass;
    }
}

public class MainMethodRunner {

    private final String mainClassName;

    private final String[] args;

    /**
     * Create a new {@link MainMethodRunner} instance.
     * @param mainClass the main class
     * @param args incoming arguments
     */
    public MainMethodRunner(String mainClass, String[] args) {
       this.mainClassName = mainClass;
       this.args = (args != null) ? args.clone() : null;
    }

    public void run() throws Exception {
       // 从线程上下文中的类加载器中加载我们的服务启动类
       Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
       // 反射获取到 main 方法进行执行
       Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
       mainMethod.setAccessible(true);
       mainMethod.invoke(null, new Object[] { this.args });
    }

}

至此,我们应用程序主入口类的main方法被调用执行,所有应用程序类文件均可通过/BOOT-INF/classes加载,所有依赖的第三方jar均可通过/BOOT-INF/lib加载。

相关推荐
JanelSirry3 小时前
Java + Spring Boot + Redis技术栈,在实际使用缓存时遇到 缓存击穿、缓存穿透、缓存雪崩
java·spring boot·缓存
陈果然DeepVersion4 小时前
Java大厂面试真题:Spring Boot+Kafka+AI智能客服场景全流程解析(十二)
java·spring boot·ai·kafka·面试题·向量数据库·rag
陈果然DeepVersion4 小时前
Java大厂面试真题:Spring Boot+Kafka+AI智能客服场景全流程解析(九)
java·人工智能·spring boot·微服务·kafka·面试题·rag
爆爆凯4 小时前
Spring Boot Web上下文工具类详解:获取Request、Response和参数
前端·spring boot·后端
行思理5 小时前
Lombok 新手教程
java·spring boot·lombok
观望过往5 小时前
Spring Boot 集成 InfluxDB 2.x 完整技术指南
java·spring boot·influxdb
摇滚侠14 小时前
Spring Boot3零基础教程,Spring Boot 应用打包成 exe 可执行文件,笔记91 笔记92 笔记93
linux·spring boot·笔记
lang2015092816 小时前
Spring Boot日志配置完全指南
java·spring boot·单元测试
故事不长丨16 小时前
【Java SpringBoot+Vue 实现视频文件上传与存储】
java·javascript·spring boot·vscode·后端·vue·intellij-idea