千家信息网

SpringBoot为什么可以使用Jar包启动

发表于:2025-11-12 作者:千家信息网编辑
千家信息网最后更新 2025年11月12日,这篇文章将为大家详细讲解有关SpringBoot为什么可以使用Jar包启动,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。引言很多初学者会比较困惑,Spring Bo
千家信息网最后更新 2025年11月12日SpringBoot为什么可以使用Jar包启动

这篇文章将为大家详细讲解有关SpringBoot为什么可以使用Jar包启动,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

    引言

    很多初学者会比较困惑,Spring Boot 是如何做到将应用代码和所有的依赖打包成一个独立的 Jar 包,因为传统的 Java 项目打包成 Jar 包之后,需要通过 -classpath 属性来指定依赖,才能够运行。

    Spring Boot 打包插件

    Spring Boot 提供了一个名叫 spring-boot-maven-plugin 的 maven 项目打包插件,如下:

        org.springframework.boot    spring-boot-maven-plugin

    可以方便的将 Spring Boot 项目打成 jar 包。 这样我们就不再需要部署 Tomcat 、Jetty等之类的 Web 服务器容器啦。

    我们先看一下 Spring Boot 打包后的结构是什么样的,打开 target 目录我们发现有两个jar包:

    其中,springboot-0.0.1-SNAPSHOT.jar 是通过 Spring Boot 提供的打包插件采用新的格式打成 Fat Jar,包含了所有的依赖;

    springboot-0.0.1-SNAPSHOT.jar.original 则是Java原生的打包方式生成的,仅仅只包含了项目本身的内容。

    SpringBoot FatJar 的组织结构

    我们将 Spring Boot 打的可执行 Jar 展开后的结构如下所示:

    • BOOT-INF目录:包含了我们的项目代码(classes目录),以及所需要的依赖(lib 目录);

    • META-INF目录:通过 MANIFEST.MF 文件提供 Jar包的元数据,声明了 jar 的启动类;

    • org.springframework.boot.loader :Spring Boot 的加载器代码,实现的 Jar in Jar 加载的魔法源。

    我们看到,如果去掉BOOT-INF目录,这将是一个非常普通且标准的Jar包,包括元信息以及可执行的代码部分,其/META-INF/MAINFEST.MF指定了Jar包的启动元信息,org.springframework.boot.loader 执行对应的逻辑操作。

    MAINFEST.MF 元信息

    元信息内容如下所示:

    Manifest-Version: 1.0Spring-Boot-Classpath-Index: BOOT-INF/classpath.idxImplementation-Title: springbootImplementation-Version: 0.0.1-SNAPSHOTSpring-Boot-Layers-Index: BOOT-INF/layers.idxStart-Class: com.listenvision.SpringbootApplicationSpring-Boot-Classes: BOOT-INF/classes/Spring-Boot-Lib: BOOT-INF/lib/Build-Jdk-Spec: 1.8Spring-Boot-Version: 2.5.6Created-By: Maven Jar Plugin 3.2.0Main-Class: org.springframework.boot.loader.JarLauncher

    它相当于一个 Properties 配置文件,每一行都是一个配置项目。重点来看看两个配置项:

    • Main-Class 配置项:Java 规定的 jar 包的启动类,这里设置为 spring-boot-loader 项目的 JarLauncher 类,进行 Spring Boot 应用的启动。

    • Start-Class 配置项:Spring Boot 规定的主启动类,这里设置为我们定义的 Application 类。

    • Spring-Boot-Classes 配置项:指定加载应用类的入口。

    • Spring-Boot-Lib 配置项: 指定加载应用依赖的库。

    启动原理

    Spring Boot 的启动原理如下图所示:

    源码分析

    JarLauncher

    JarLauncher 类是针对 Spring Boot jar 包的启动类, 完整的类图如下所示:

    其中的 WarLauncher 类,是针对 Spring Boot war 包的启动类。 启动类 org.springframework.boot.loader.JarLauncher 并非为项目中引入类,而是 spring-boot-maven-plugin 插件 repackage 追加进去的。

    接下来我们先来看一下 JarLauncher 的源码,比较简单,如下图所示:

    public class JarLauncher extends ExecutableArchiveLauncher {    private static final String DEFAULT_CLASSPATH_INDEX_LOCATION = "BOOT-INF/classpath.idx";    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 ClassPathIndexFile getClassPathIndex(Archive archive) throws IOException {        // Only needed for exploded archives, regular ones already have a defined order        if (archive instanceof ExplodedArchive) {            String location = getClassPathIndexFileLocation(archive);            return ClassPathIndexFile.loadIfPossible(archive.getUrl(), location);        }        return super.getClassPathIndex(archive);    }           private String getClassPathIndexFileLocation(Archive archive) throws IOException {        Manifest manifest = archive.getManifest();        Attributes attributes = (manifest != null) ? manifest.getMainAttributes() : null;        String location = (attributes != null) ? attributes.getValue(BOOT_CLASSPATH_INDEX_ATTRIBUTE) : null;        return (location != null) ? location : DEFAULT_CLASSPATH_INDEX_LOCATION;    }        @Override    protected boolean isPostProcessingClassPathArchives() {        return false;    }        @Override    protected boolean isSearchCandidate(Archive.Entry entry) {        return entry.getName().startsWith("BOOT-INF/");    }        @Override    protected boolean isNestedArchive(Archive.Entry entry) {        return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);    }        public static void main(String[] args) throws Exception {        //调用基类 Launcher 定义的 launch 方法        new JarLauncher().launch(args);    }}

    主要看它的 main 方法,调用的是基类 Launcher 定义的 launch 方法,而 Launcher 是ExecutableArchiveLauncher 的父类。下面我们来看看Launcher基类源码:

    Launcher

    public abstract class Launcher {    private static final String JAR_MODE_LAUNCHER = "org.springframework.boot.loader.jarmode.JarModeLauncher";    protected void launch(String[] args) throws Exception {        if (!isExploded()) {            JarFile.registerUrlProtocolHandler();        }        ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());        String jarMode = System.getProperty("jarmode");        String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();        launch(args, launchClass, classLoader);    }        @Deprecated    protected ClassLoader createClassLoader(List archives) throws Exception {        return createClassLoader(archives.iterator());    }        protected ClassLoader createClassLoader(Iterator archives) throws Exception {        List urls = new ArrayList<>(50);        while (archives.hasNext()) {            urls.add(archives.next().getUrl());        }        return createClassLoader(urls.toArray(new URL[0]));    }        protected ClassLoader createClassLoader(URL[] urls) throws Exception {        return new LaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass().getClassLoader());    }        protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {        Thread.currentThread().setContextClassLoader(classLoader);        createMainMethodRunner(launchClass, args, classLoader).run();    }        protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {        return new MainMethodRunner(mainClass, args);    }    protected abstract String getMainClass() throws Exception;        protected Iterator getClassPathArchivesIterator() throws Exception {        return getClassPathArchives().iterator();    }        @Deprecated    protected List getClassPathArchives() throws Exception {        throw new IllegalStateException("Unexpected call to getClassPathArchives()");    }        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));    }        protected boolean isExploded() {        return false;    }        protected Archive getArchive() {        return null;    }}
    • launch 方法会首先创建类加载器,而后判断是否 jar 是否在 MANIFEST.MF 文件中设置了 jarmode 属性。

    • 如果没有设置,launchClass 的值就来自 getMainClass() 返回,该方法由PropertiesLauncher子类实现,返回 MANIFEST.MF 中配置的 Start-Class 属性值。

    • 调用 createMainMethodRunner 方法,构建一个 MainMethodRunner 对象并调用其 run 方法。

    PropertiesLauncher

    @Overrideprotected String getMainClass() throws Exception {    //加载 jar包 target目录下的  MANIFEST.MF 文件中 Start-Class配置,找到springboot的启动类    String mainClass = getProperty(MAIN, "Start-Class");    if (mainClass == null) {        throw new IllegalStateException("No '" + MAIN + "' or 'Start-Class' specified");    }    return mainClass;}

    MainMethodRunner

    目标类main方法的执行器,此时的 mainClassName 被赋值为 MANIFEST.MF 中配置的 Start-Class 属性值,也就是 com.listenvision.SpringbootApplication,之后便是通过反射执行 SpringbootApplication 的 main 方法,从而达到启动 Spring Boot 的效果。

    public class MainMethodRunner {    private final String mainClassName;    private final String[] args;    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());        Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);        mainMethod.setAccessible(true);        mainMethod.invoke(null, new Object[] { this.args });    }}

    关于"SpringBoot为什么可以使用Jar包启动"这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

    配置 方法 项目 目录 代码 信息 属性 插件 文件 应用 内容 源码 篇文章 结构 两个 原理 更多 不错 实用 普通 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 云服务器怎么玩不了 东莞手机游戏软件开发方向 互联网学术资源数据库包括哪些 还原数据库的语句 服务器管理思维导图 app软件开发项目文档 怎么开国际版我的世界服务器 遗传算法是哪个数据库的检索方法 全国打拐DNA数据库在哪里录入 济南学软件开发哪所学校好 南京软件开发定制公司有哪些 陕西库存管理软件开发公司 乐园数据库 爱奇艺网络安全周视频下载 小牛哥整家互联网科技官网 六安咖啡点餐软件开发定制 山西智能软件开发哪家专业 rockstar数据库连接失败 软件开发入门的书籍 房山服务器硬盘回收报价 云服务器智能制造 网络安全平安校园手抄报 我的世界暗墨服务器多人生存 黄山安徽联想服务器代理商 混合软件开发什么意思 欧盟网络安全法案2019 虹口区创新软件开发厂家范围 计算机网络技术基础护肤步骤 杭州上位机软件开发工程师 非关系型数据库怎么画数据关系图
    0