为什么SpringBoot的 jar 可以直接运行?

一、简述

在使用 SpringBoot 打包时会出现一些奇怪的疑问:

1.SpringBoot项目打成的jar包,通过执行命令 java -jar xxx.jar,可以直接运行?

2.SpringBoot项目打成的jar包,被其他项目依赖之后,总是报找不到类的错误?

3.SpringBoot项目执行打包命令后,会出现***.jar及***.jar.original,两个文件?

SpringBoot中默认打包成的 jar 叫做 可执行 jar,也称作Uber JAR,也被戏称为胖Jar(Fat JAR)

Fat JAR被用于构建一种完全自包含且可独立运行的应用程序包。它不同于普通的 jar包(普通的jar包主要是被其他应用依赖),SpringBoot 打成的 jar包可以执行,但是不可以被其他的应用所依赖,即使强制依赖,也无法获取里边的类。

Fat JAR不仅仅包含项目的主代码,还包括了所有必要的第三方库、资源文件等一切运行时所需要的组件,意味着只需分发这一个文件即可部署应用,无需再额外处理众多的依赖库。

这种形式极大地方便了应用的快速部署与迁移,尤其适合于云端部署或者无网络环境下的安装。

普通jar包来说,它通常仅包含一个模块或应用程序的一部分,主要用来封装和组织Java类及相关资源。

二、机制

1、打包

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

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

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.5.15</version>
        </plugin>
    </plugins>
</build>

 插件spring-boot-maven-plugin,提供了7个maven目标:

1. build-image: 将程序使用 buildpack 打包进容器镜像中。
2. build-info:生成项目的构建信息文件 build-info.properties。根据当前 MavenProject 的内容生成一个 - build-info.properties 文件。
3. help:显示帮助信息。调用mvn spring-boot:help -Ddetail=true -Dgoal=以显示参数详细信息。
4. repackage:可生成可执行的jar包或war包。插件的核心目标。 默认的目标,将普通 mvn package 打包成的 jar 重新打包成包含所有程序依赖项的可执行 jar/war 文件,并保留 mvn  package 打包的 jar 为 .original 后缀。
5. run:运行 Spring Boot 应用。
6. start:在集成测试阶段,控制生命周期。通常用于集成测试方案中,在 mvn integration-test 阶段管理 Spring Boot 应用的生命周期。
7. stop:在集成测试阶段,控制生命周期。停止已通过 start 目标启动的应用程序。通常在 integration-- test 完成后调用。

打包完成后,会出现两个文件:

ruoyi-admin.jar为可运行的Fat JAR,ruoyi-admin.jar.original为mvn package 打的 jar包;

将Fat JAR 解压能看到下面的结构:

├─BOOT-INF
│  ├─classes
│  └─lib
├─META-INF
│  ├─maven
│  ├─app.properties
│  ├─MANIFEST.MF      
└─org
    └─springframework
        └─boot
            └─loader
                ├─archive
                ├─data
                ├─jar
                └─util

                └─其他class文件...

对比Fat JAR与普通jar包,发现:

1. 源 jar 中主项目的 .class 文件被移至Fat JAR 的 BOOT-INF/classes 文件夹下。
2. 新增 BOOT-INF/lib 文件夹,里面存放三方 jar 文件。
3. 新增 BOOT-INF/classpath.idx,用来记录 classpath 的加载顺序。
4. 新增 org/springframework/boot/loader 文件夹,这是 spring-boot-loader 编译后的 .class 文件。
5. 清单文件 MANIFEST.MF中新增以下属性:
        Spring-Boot-Classpath-Index: 记录 classpath.idx 文件的地址。
        Start-Class: 指定 Spring Boot 的启动类。
        Spring-Boot-Classes: 记录主项目的 .class 文件存放路径。
        Spring-Boot-Lib: 记录三方 jar 文件存放路径。
        Spring-Boot-Version: 记录 Spring Boot 版本信息
        Main-Class: 指定 jar 程序的入口类(可执行jar为org.springframework.boot.loader.JarLauncher类)。

2、启动

Fat JAR可以直接运行主要依赖于它的启动器以及Loader机制,而对于Loader机制主要利用MANIFEST.MF文件以及其内部类加载逻辑。

MANIFEST.MF是JAR文件内的一个标准元数据文件,它包含了关于JAR包的基本信息和运行指令。在Spring Boot应用的jar包中,MANIFEST.MF尤为重要,因为它设置了Main-Class属性,指示了用于启动整个应用程序的类,这个类通常是org.springframework.boot.loader.JarLauncher或其他由Spring Boot提供的启动器类。

Main-Class属性指向的JarLauncher类是Spring Boot自定义的类加载器体系的一部分。JarLauncher继承自org.springframework.boot.loader.Launcher,专门用于启动以Fat JAR形式发布的Spring Boot应用。JarLauncher负责创建一个类加载器LaunchedURLClassLoader。 

public class JarLauncher extends ExecutableArchiveLauncher {

	static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";

	static final String BOOT_INF_LIB = "BOOT-INF/lib/";

	public JarLauncher() {
	}

	protected JarLauncher(Archive archive) {
		super(archive);
	}

	@Override
	protected boolean isNestedArchive(Archive.Entry entry) {
		if (entry.isDirectory()) {
			return entry.getName().equals(BOOT_INF_CLASSES);
		}
		return entry.getName().startsWith(BOOT_INF_LIB);
	}

	public static void main(String[] args) throws Exception {
        //项目入口,重点在launch这个方法中
		new JarLauncher().launch(args);
	}
}
//launch方法
protected void launch(String[] args) throws Exception {
    JarFile.registerUrlProtocolHandler();
    //创建LaunchedURLClassLoader。如果根类加载器和扩展类加载器没有加载到某个类的话,就会通过LaunchedURLClassLoader这个加载器来加载类。这个加载器会从Boot-INF下面的class目录和lib目录下加载类。
    ClassLoader classLoader = createClassLoader(getClassPathArchives());
    //这个方法会读取jar描述文件中的Start-Class属性,然后通过反射调用到这个类的main方法。
    launch(args, getMainClass(), classLoader);
}  

简单来说
Spring Boot 的Fat JAR的入口点是 JarLauncher 的 main 方法;

该方法的执行逻辑是先创建一个 LaunchedURLClassLoader,这个加载器加载类的逻辑是:

先判断根类加载器和扩展类加载器能否加载到某个类,如果都加载不到就从 Boot-INF 下面的 class 和 lib 目录下去加载;

读取Start-Class属性,通过反射机制调用启动类的 main 方法,这样就顺利调用到我们开发的 Spring Boot 主启动类的 main 方法了。

3、内嵌Web容器

传统的Java应用程序开发中,对于基于Servlet规范的Java Web应用,开发完成后通常会被打包成WAR格式,然后部署到像Apache Tomcat、Jetty这样的Web容器中。

Spring Boot的一大特色就是能够无缝整合并内嵌多种轻量级Web容器,比如:Apache Tomcat、Jetty、Undertow以及Reactor Netty。不再需要在本地或服务器上独立安装和配置Web服务器(例如以前还需要在本地安装tomcat)。

当Spring Boot应用引入了spring-boot-starter-web依赖时,默认情况下会自动配置并启动一个内嵌的Web容器。在Spring Boot启动的过程中,内嵌容器作为应用的一部分被初始化并绑定到特定端口上,以便对外提供HTTP服务。

4、生成普通的jar包

1. 插件注释掉,重新打包。

2. 通过命令

mvn clean package -D spring-boot.repackage.skip=true

3. 通过配置

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <skip>true</skip>
    </configuration>
</plugin> 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值