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

相关推荐
Tirzano2 小时前
SpringOAuth2Server 自定义授权码认证,登录和授权码混合
spring boot
一 乐3 小时前
饮食营养信息|基于springboot + vue饮食营养管理信息平台系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·饮食营养管理信息系统
Devin~Y4 小时前
大厂Java面试实战:Spring Boot/WebFlux、Redis+Kafka、K8s可观测性与Spring AI RAG/Agent三轮连环问
java·spring boot·redis·kafka·kubernetes·resilience4j·spring webflux
悟空码字4 小时前
别再重复造轮子了!SpringBoot对接第三方系统模板,拿来即用
java·spring boot·后端
indexsunny4 小时前
互联网大厂Java求职面试实战:Spring Boot与微服务架构解析
java·spring boot·redis·kafka·spring security·flyway·microservices
我叫张土豆4 小时前
让 AI 学会用工具:基于 LangChain4j 的 Skills Agent 全栈落地实战
人工智能·spring boot
我登哥MVP4 小时前
【SpringMVC笔记】 - 2 - @RequestMapping
java·spring boot·spring·servlet·tomcat·intellij-idea·springmvc
常利兵5 小时前
从0到1:Spring Boot 中WebSocket实战揭秘,开启实时通信新时代
spring boot·后端·websocket
希望永不加班6 小时前
SpringBoot 依赖管理:BOM 与版本控制
java·spring boot·后端·spring
勿忘,瞬间6 小时前
Spring Boot
java·数据库·spring boot