千家信息网

Spring Boot打的包为什么能直接运行

发表于:2026-11-03 作者:千家信息网编辑
千家信息网最后更新 2026年11月03日,这篇文章主要讲解了"Spring Boot打的包为什么能直接运行",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"Spring Boot打的包为什么能直接
千家信息网最后更新 2026年11月03日Spring Boot打的包为什么能直接运行

这篇文章主要讲解了"Spring Boot打的包为什么能直接运行",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"Spring Boot打的包为什么能直接运行"吧!

Spring Boot 提供了一个插件 spring-boot-maven-plugin 用于把程序打包成一个可执行的jar包。

在pom文件里加入这个插件即可:

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

打包完生成的executable-jar-1.0-SNAPSHOT.jar内部的结构如下:

├── META-INF  │   ├── MANIFEST.MF  │   └── maven  │       └── spring.study  │           └── executable-jar  │               ├── pom.properties  │               └── pom.xml  ├── lib  │   ├── aopalliance-1.0.jar  │   ├── classmate-1.1.0.jar  │   ├── spring-boot-1.3.5.RELEASE.jar  │   ├── spring-boot-autoconfigure-1.3.5.RELEASE.jar  │   ├── ...  ├── org  │   └── springframework  │       └── boot  │           └── loader  │               ├── ExecutableArchiveLauncher$1.class  │               ├── ...  └── spring      └── study          └── executablejar              └── ExecutableJarApplication.class

然后可以直接执行jar包就能启动程序了:

java -jar executable-jar-1.0-SNAPSHOT.jar

运行 Spring Boot 应用的几种方式看这篇:运行 Spring Boot 应用的3种方式。

打包出来fat jar内部有4种文件类型:

  1. META-INF文件夹:程序入口,其中MANIFEST.MF用于描述jar包的信息

  2. lib目录:放置第三方依赖的jar包,比如springboot的一些jar包

  3. spring boot loader相关的代码

  4. 模块自身的代码

MANIFEST.MF文件的内容:

Manifest-Version: 1.0  Implementation-Title: executable-jar  Implementation-Version: 1.0-SNAPSHOT  Archiver-Version: Plexus Archiver  Built-By: Format  Start-Class: spring.study.executablejar.ExecutableJarApplication  Implementation-Vendor-Id: spring.study  Spring-Boot-Version: 1.3.5.RELEASE  Created-By: Apache Maven 3.2.3  Build-Jdk: 1.8.0_20  Implementation-Vendor: Pivotal Software, Inc.  Main-Class: org.springframework.boot.loader.JarLauncher

我们看到,它的Main-Class是org.springframework.boot.loader.JarLauncher,当我们使用java -jar执行jar包的时候会调用JarLauncher的main方法,而不是我们编写的SpringApplication。

那么JarLauncher这个类是的作用是什么的?

它是SpringBoot内部提供的工具Spring Boot Loader提供的一个用于执行Application类的工具类(fat jar内部有spring loader相关的代码就是因为这里用到了)。相当于Spring Boot Loader提供了一套标准用于执行SpringBoot打包出来的jar。

关注微信公众号:Java技术栈,在后台回复:boot,可以获取我整理的 N 篇 Spring boot 教程,都是干货。

Spring Boot Loader抽象的一些类

抽象类Launcher:各种Launcher的基础抽象类,用于启动应用程序;跟Archive配合使用;目前有3种实现,分别是JarLauncher、WarLauncher以及PropertiesLauncher。

Archive:归档文件的基础抽象类。JarFileArchive就是jar包文件的抽象。它提供了一些方法比如getUrl会返回这个Archive对应的URL;getManifest方法会获得Manifest数据等。ExplodedArchive是文件目录的抽象

JarFile:对jar包的封装,每个JarFileArchive都会对应一个JarFile。JarFile被构造的时候会解析内部结构,去获取jar包里的各个文件或文件夹,这些文件或文件夹会被封装到Entry中,也存储在JarFileArchive中。如果Entry是个jar,会解析成JarFileArchive。

比如一个JarFileArchive对应的URL为:

jar:file:/Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/

它对应的JarFile为:

/Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar

这个JarFile有很多Entry,比如:

META-INF/  META-INF/MANIFEST.MF  spring/  spring/study/  ....  spring/study/executablejar/ExecutableJarApplication.class  lib/spring-boot-starter-1.3.5.RELEASE.jar  lib/spring-boot-1.3.5.RELEASE.jar  ...

JarFileArchive内部的一些依赖jar对应的URL(SpringBoot使用org.springframework.boot.loader.jar.Handler处理器来处理这些URL):

jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-starter-web-1.3.5.RELEASE.jar!/ jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/org/springframework/boot/loader/JarLauncher.class

我们看到如果有jar包中包含jar,或者jar包中包含jar包里面的class文件,那么会使用 !/ 分隔开,这种方式只有org.springframework.boot.loader.jar.Handler能处理,它是 Spring Boot 内部扩展出来的一种URL协议。

JarLauncher的执行过程

JarLauncher的main方法:

public static void main(String\[\] args) {     // 构造JarLauncher,然后调用它的launch方法。参数是控制台传递的     new JarLauncher().launch(args);    }

JarLauncher被构造的时候会调用父类ExecutableArchiveLauncher的构造方法。

ExecutableArchiveLauncher的构造方法内部会去构造Archive,这里构造了JarFileArchive。构造JarFileArchive的过程中还会构造很多东西,比如JarFile,Entry …

JarLauncher的launch方法:

protected void launch(String\[\] args) {     try {     // 在系统属性中设置注册了自定义的URL处理器:org.springframework.boot.loader.jar.Handler。如果URL中没有指定处理器,会去系统属性中查询     JarFile.registerUrlProtocolHandler();     // getClassPathArchives方法在会去找lib目录下对应的第三方依赖JarFileArchive,同时也会项目自身的JarFileArchive     // 根据getClassPathArchives得到的JarFileArchive集合去创建类加载器ClassLoader。这里会构造一个LaunchedURLClassLoader类加载器,这个类加载器继承URLClassLoader,并使用这些JarFileArchive集合的URL构造成URLClassPath     // LaunchedURLClassLoader类加载器的父类加载器是当前执行类JarLauncher的类加载器    ClassLoader classLoader = createClassLoader(getClassPathArchives());     // getMainClass方法会去项目自身的Archive中的Manifest中找出key为Start-Class的类     // 调用重载方法launch     launch(args, getMainClass(), classLoader);     }     catch (Exception ex) {     ex.printStackTrace();     System.exit(1);     }    }    // Archive的getMainClass方法    // 这里会找出spring.study.executablejar.ExecutableJarApplication这个类    public String getMainClass() throws Exception {     Manifest manifest = getManifest();     String mainClass = null;     if (manifest != null) {     mainClass = manifest.getMainAttributes().getValue("Start-Class");     }     if (mainClass == null) {     throw new IllegalStateException(     "No 'Start-Class' manifest entry specified in " \+ this);     }     return mainClass;    }    // launch重载方法    protected void launch(String\[\] args, String mainClass, ClassLoader classLoader)     throws Exception {     // 创建一个MainMethodRunner,并把args和Start-Class传递给它     Runnable runner = createMainMethodRunner(mainClass, args, classLoader);     // 构造新线程     Thread runnerThread = new Thread(runner);     // 线程设置类加载器以及名字,然后启动     runnerThread.setContextClassLoader(classLoader);   runnerThread.setName(Thread.currentThread().getName());     runnerThread.start();    }

MainMethodRunner的run方法:

@Override    public void run() {     try {     // 根据Start-Class进行实例化   Class mainClass = Thread.currentThread().getContextClassLoader()     .loadClass(this.mainClassName);     // 找出main方法     Method mainMethod = mainClass.getDeclaredMethod("main", String\[\].class);     // 如果main方法不存在,抛出异常     if (mainMethod == null) {     throw new IllegalStateException(     this.mainClassName + " does not have a main method");     }     // 调用     mainMethod.invoke(null, new Object\[\] { this.args });     }     catch (Exception ex) {     UncaughtExceptionHandler handler = Thread.currentThread()     .getUncaughtExceptionHandler();     if (handler != null) {     handler.uncaughtException(Thread.currentThread(), ex);     }     throw new RuntimeException(ex);     }    }

Start-Class的main方法调用之后,内部会构造Spring容器,启动内置Servlet容器等过程。这些过程我们都已经分析过了。Spring Boo主类及目录结构介绍,这篇看下。

关于自定义的类加载器LaunchedURLClassLoader

LaunchedURLClassLoader重写了loadClass方法,也就是说它修改了默认的类加载方式(先看该类是否已加载这部分不变,后面真正去加载类的规则改变了,不再是直接从父类加载器中去加载)。LaunchedURLClassLoader定义了自己的类加载规则:

private Class doLoadClass(String name) throws ClassNotFoundException {    // 1) Try the root class loader     try {     if (this.rootClassLoader != null) {    return this.rootClassLoader.loadClass(name);     }     }     catch (Exception ex) {     // Ignore and continue     }     // 2) Try to find locally     try {     findPackage(name);     Class cls = findClass(name);    return cls;     }     catch (Exception ex) {     // Ignore and continue     }     // 3) Use standard loading     return super.loadClass(name, false);    }

加载规则:

  1. 如果根类加载器存在,调用它的加载方法。这里是根类加载是ExtClassLoader

  2. 调用LaunchedURLClassLoader自身的findClass方法,也就是URLClassLoader的findClass方法

  3. 调用父类的loadClass方法,也就是执行默认的类加载顺序(从BootstrapClassLoader开始从下往下寻找)

LaunchedURLClassLoader自身的findClass方法:

protected Class findClass(final String name)   throws ClassNotFoundException    {     try {     return AccessController.doPrivileged(     new PrivilegedExceptionAction>() {     public Class run() throws ClassNotFoundException {     // 把类名解析成路径并加上.class后缀     String path = name.replace('.', '/').concat(".class");     // 基于之前得到的第三方jar包依赖以及自己的jar包得到URL数组,进行遍历找出对应类名的资源     // 比如path是org/springframework/boot/loader/JarLauncher.class,它在jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/中被找出     // 那么找出的资源对应的URL为jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/org/springframework/boot/loader/JarLauncher.class     Resource res = ucp.getResource(path, false);     if (res != null) { // 找到了资源     try {     return defineClass(name, res);     } catch (IOException e) {     throw new ClassNotFoundException(name, e);  }     } else { // 找不到资源的话直接抛出ClassNotFoundException异常     throw new ClassNotFoundException(name);     }     }     }, acc);     } catch (java.security.PrivilegedActionException pae) {  throw (ClassNotFoundException) pae.getException();    }    }

下面是LaunchedURLClassLoader的一个测试:

// 注册org.springframework.boot.loader.jar.Handler URL协议处理器    JarFile.registerUrlProtocolHandler();    // 构造LaunchedURLClassLoader类加载器,这里使用了2个URL,分别对应jar包中依赖包spring-boot-loader和spring-boot,使用 "!/" 分开,需要org.springframework.boot.loader.jar.Handler处理器处理   LaunchedURLClassLoader classLoader = new LaunchedURLClassLoader(     new URL\[\] {     new URL("jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/")     , new URL("jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-1.3.5.RELEASE.jar!/")     },    LaunchedURLClassLoaderTest.class.getClassLoader());    // 加载类    // 这2个类都会在第二步本地查找中被找出(URLClassLoader的findClass方法)    classLoader.loadClass("org.springframework.boot.loader.JarLauncher");    classLoader.loadClass("org.springframework.boot.SpringApplication");    // 在第三步使用默认的加载顺序在ApplicationClassLoader中被找出    classLoader.loadClass("org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration");

Spring Boot Loader的作用

Spring Boot 在可执行jar包中定义了自己的一套规则,比如第三方依赖jar包在/lib目录下,jar包的URL路径使用自定义的规则并且这个规则需要使用org.springframework.boot.loader.jar.Handler处理器处理。

它的Main-Class使用JarLauncher,如果是war包,使用WarLauncher执行。这些Launcher内部都会另起一个线程启动自定义的SpringApplication类。

这些特性通过spring-boot-maven-plugin插件打包完成。

感谢各位的阅读,以上就是"Spring Boot打的包为什么能直接运行"的内容了,经过本文的学习后,相信大家对Spring Boot打的包为什么能直接运行这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

方法 文件 处理 运行 处理器 规则 目录 方式 程序 第三方 资源 过程 也就是 代码 内容 就是 插件 文件夹 时候 线程 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 西安纸贵互联网科技有限公司 金融科技还是互联网 金融科技创新监管互联网金融 与互联网科技公司 中国 科技 互联网 朗涯互联网科技 宜安科技 互联网金融 天下捷融互联网科技 齐家互联网科技有限公司 互联网高科技股票 互联网科技公司介绍 无锡溥仪互联网科技有限公司 互联网经济与金融科技 昭通互联网科技选哪家 玉溪互联网科技怎么选 中科明德北京互联网科技 互联网金融科技公司的优势 广州千川互联网科技有限公司 皮皮虾互联网科技有限公司 互联网科技职业技能培训学校 湖北九亚互联网络科技有限公司 互联网为科技 高科技与互联网 栖莱科技互联网股份有限公司 河南猎农互联网科技有限公司 未来五年的互联网科技 互联网科技大奖都有哪些公司 中科明德北京互联网科技 互联网科技手机排行榜 互联网 科技节活动 微视频大赛
0