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

相关推荐
代码or搬砖4 小时前
RBAC(权限认证)小例子
java·数据库·spring boot
咸鱼加辣5 小时前
【java面试题】springboot的生命周期
java·开发语言·spring boot
VX:Fegn08957 小时前
计算机毕业设计|基于springboot + vue敬老院管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
北漂IT民工_程序员_ZG8 小时前
SpringBean生命周期,动态代理
java·spring boot·spring
老华带你飞8 小时前
建筑材料管理|基于springboot 建筑材料管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习·spring
ss2739 小时前
SpringBoot+vue养老院运营管理系统
vue.js·spring boot·后端
是梦终空10 小时前
JAVA毕业设计259—基于Java+Springboot+vue3工单管理系统的设计与实现(源代码+数据库+开题报告)
java·spring boot·vue·毕业设计·课程设计·工单管理系统·源代码
程序员欣宸12 小时前
LangChain4j实战之四:集成到spring-boot
java·人工智能·spring boot
计算机毕设VX:Fegn089512 小时前
计算机毕业设计|基于springboot + vue酒店管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计