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加载。

相关推荐
骄马之死33 分钟前
SpringMVC + SpringBoot 核心知识点总结
java·spring boot·后端
郑洁文2 小时前
基于Spring Boot的流浪动物救助网站
java·spring boot·后端·毕设·流浪动物救助
指令集梦境4 小时前
Cursor + Spring Boot实战:从零写一个RESTful API
spring boot·后端·restful
普通网友6 小时前
springboot之集成Elasticsearch
spring boot·后端·elasticsearch
invicinble7 小时前
关于flowable流程引擎技术栈相关
spring boot
倒流时光三十年12 小时前
第十八章 搜索历史保存功能实现记录
spring boot·微信小程序
倒流时光三十年12 小时前
第十七章 投票页面增加搜索功能
spring boot·微信小程序
郑洁文13 小时前
基于Springboot的足球青训俱乐部管理系统的设计与实现
java·spring boot·后端·足球青训俱乐部管理系统
我登哥MVP13 小时前
Spring Boot 从“会用”到“精通”:自定义参数绑定原理
java·spring boot·后端·spring·servlet·maven·intellij-idea
小江的记录本14 小时前
【Spring全家桶】Spring AI核心原理、大模型集成、Prompt工程、RAG实现、AI Agent开发(附《思维导图》+《面试高频考点清单》)
java·人工智能·spring boot·后端·spring·面试·prompt