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

相关推荐
初次攀爬者15 小时前
RocketMQ在Spring Boot上的基础使用
java·spring boot·rocketmq
花花无缺15 小时前
搞懂@Autowired 与@Resuorce
java·spring boot·后端
Derek_Smart17 小时前
从一次 OOM 事故说起:打造生产级的 JVM 健康检查组件
java·jvm·spring boot
Nyarlathotep01131 天前
SpringBoot Starter的用法以及原理
java·spring boot
dkbnull2 天前
深入理解Spring两大特性:IoC和AOP
spring boot
洋洋技术笔记2 天前
Spring Boot条件注解详解
java·spring boot
洋洋技术笔记3 天前
Spring Boot配置管理最佳实践
spring boot
用户8307196840824 天前
Spring Boot 项目中日期处理的最佳实践
java·spring boot
大道至简Edward4 天前
Spring Boot 2.7 + JDK 8 升级到 Spring Boot 3.x + JDK 17 完整指南
spring boot·后端
洋洋技术笔记4 天前
Spring Boot启动流程解析
spring boot·后端