Spring Boot Fat Jar 的三种打包方式

在Spring Boot应用开发中,打包是将应用及其所有依赖整合到一个可执行文件中的过程,这种包含所有依赖的jar文件通常被称为"Fat Jar"。

一、Spring Boot Maven/Gradle插件默认打包方式

1.1 基本原理

Spring Boot提供了官方的Maven和Gradle插件,用于创建可执行的jar或war文件。

这是最常用的打包方式,也是Spring Boot官方推荐的方法。

该插件会创建一个包含应用代码、依赖库以及嵌入式容器(如果需要)的自包含可执行jar文件。

Spring Boot的jar包采用了特殊的"嵌套jar"结构,它通过自定义的JarLauncher类加载各个嵌套的jar包,避免了传统"胖jar"中的类路径问题。

1.2 配置方式

Maven配置

pom.xml文件中添加Spring Boot Maven插件:

xml 复制代码
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.7.0</version>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

执行打包命令:

go 复制代码
mvn clean package

Gradle配置

build.gradle文件中应用Spring Boot插件:

bash 复制代码
plugins {
    id 'org.springframework.boot' version '2.7.0'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

// 其他配置...

bootJar {
    archiveBaseName = 'myapp'
    archiveVersion = '1.0.0'
}

执行打包命令:

bash 复制代码
./gradlew bootJar

1.3 打包结构分析

Spring Boot Maven/Gradle插件创建的Fat Jar具有以下结构:

erlang 复制代码
myapp.jar
├── META-INF
│   ├── MANIFEST.MF
│   └── maven
│       └── ...
├── BOOT-INF
│   ├── classes
│   │   └── com
│   │       └── example
│   │           └── myapp
│   │               └── ...
│   └── lib
│       ├── dependency1.jar
│       ├── dependency2.jar
│       └── ...
└── org
    └── springframework
        └── boot
            └── loader
                └── ...

主要组成部分:

  • META-INF/MANIFEST.MF:包含启动类信息和类加载器信息
  • BOOT-INF/classes:应用的编译类文件
  • BOOT-INF/lib:应用的所有依赖jar文件
  • org/springframework/boot/loader:Spring Boot自定义的类加载器

1.4 运行方式

通过以下命令运行打包后的Fat Jar:

复制代码
java -jar myapp.jar

也可以指定配置文件或JVM参数:

ini 复制代码
java -Dspring.profiles.active=prod -Xmx512m -jar myapp.jar

1.5 高级配置

可以通过Spring Boot插件配置自定义打包行为:

Maven

xml 复制代码
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <!-- 排除指定依赖 -->
        <excludes>
            <exclude>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </exclude>
        </excludes>
        <!-- 指定主类 -->
        <mainClass>com.example.myapp.Application</mainClass>
        <!-- 包含系统范围依赖 -->
        <includeSystemScope>true</includeSystemScope>
        <!-- 排除开发工具 -->
        <excludeDevtools>true</excludeDevtools>
        <!-- 创建分层jar -->
        <layers>
            <enabled>true</enabled>
        </layers>
    </configuration>
</plugin>

Gradle

ini 复制代码
bootJar {
    archiveBaseName = 'myapp'
    archiveVersion = '1.0.0'
    mainClass = 'com.example.myapp.Application'
    
    // 排除特定依赖
    excludeDevtools = true
    
    // 启用分层支持
    layered {
        enabled = true
    }
}

1.6 优缺点分析

优点:

  • 官方支持,与Spring Boot完全兼容
  • 采用嵌套jar结构,避免了依赖冲突
  • 支持直接运行
  • 提供丰富的配置选项

缺点:

  • 启动时间可能略长,因为需要解析嵌套jar
  • 特殊的jar结构不符合标准jar规范
  • 某些工具可能无法正确处理嵌套jar结构
  • 排查类加载问题相对复杂

1.7 适用场景

  • 标准Spring Boot应用部署
  • 容器化部署(Docker)
  • 云平台部署
  • 需要完整Spring Boot功能的场景

二、Maven Shade插件打包方式

2.1 基本原理

Maven Shade插件是一个通用的打包工具,不仅限于Spring Boot应用。

它通过将所有依赖解压并重新打包到一个jar文件中,创建一个包含所有类文件的单一jar包。

与Spring Boot插件不同,Shade创建的jar包没有嵌套结构,所有类都位于同一个类路径下。

2.2 配置方式

pom.xml文件中添加Maven Shade插件:

xml 复制代码
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>3.2.4</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <transformers>
                            <!-- 合并META-INF/spring.factories文件 -->
                            <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                <resource>META-INF/spring.factories</resource>
                            </transformer>
                            <!-- 合并META-INF/spring.handlers文件 -->
                            <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                <resource>META-INF/spring.handlers</resource>
                            </transformer>
                            <!-- 合并META-INF/spring.schemas文件 -->
                            <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                <resource>META-INF/spring.schemas</resource>
                            </transformer>
                            <!-- 设置主类 -->
                            <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                <mainClass>com.example.myapp.Application</mainClass>
                            </transformer>
                        </transformers>
                        <!-- 过滤一些文件 -->
                        <filters>
                            <filter>
                                <artifact>*:*</artifact>
                                <excludes>
                                    <exclude>META-INF/*.SF</exclude>
                                    <exclude>META-INF/*.DSA</exclude>
                                    <exclude>META-INF/*.RSA</exclude>
                                </excludes>
                            </filter>
                        </filters>
                        <!-- 创建可执行jar -->
                        <createDependencyReducedPom>false</createDependencyReducedPom>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

执行打包命令:

go 复制代码
mvn clean package

2.3 打包结构分析

Shade插件创建的Fat Jar具有以下结构:

erlang 复制代码
myapp.jar
├── META-INF
│   ├── MANIFEST.MF
│   ├── spring.factories
│   ├── spring.handlers
│   ├── spring.schemas
│   └── maven
│       └── ...
├── com
│   └── example
│       └── myapp
│           └── ...
├── org
│   └── springframework
│       └── ...
└── ...

主要特点:

  • 所有依赖的类文件都被解压并重新打包到同一个jar中
  • 没有嵌套jar结构,所有类都在同一个类路径下
  • 需要特殊处理合并META-INF下的特殊文件

2.4 运行方式

与Spring Boot插件创建的jar包相同,可以通过以下命令运行:

复制代码
java -jar myapp.jar

2.5 高级配置

处理类冲突

当多个依赖包含相同的类时,可以使用relocation功能重命名包名以避免冲突:

xml 复制代码
<configuration>
    <relocations>
        <relocation>
            <pattern>com.google.common</pattern>
            <shadedPattern>com.example.shaded.com.google.common</shadedPattern>
        </relocation>
    </relocations>
</configuration>

最小化最终jar

可以配置Shade插件仅包含必要的类:

xml 复制代码
<configuration>
    <minimizeJar>true</minimizeJar>
</configuration>

自定义输出文件名

xml 复制代码
<configuration>
    <finalName>myapp-${project.version}-fat</finalName>
</configuration>

2.6 优缺点分析

优点:

  • 创建标准jar结构,兼容性好
  • 支持类重定位,解决依赖冲突
  • 可以最小化最终jar大小
  • 可以处理非Spring Boot应用

缺点:

  • 需要手动处理META-INF资源文件合并
  • 可能存在类路径冲突问题
  • 配置相对复杂
  • 对于大型应用,打包过程可能较慢

2.7 适用场景

  • 需要标准jar结构的场景
  • 非Spring Boot应用
  • 有特殊类路径需求的项目
  • 需要最小化最终jar大小的场景
  • 需要处理复杂依赖冲突的项目

三、Maven Assembly插件打包方式

3.1 基本原理

Maven Assembly插件是一个灵活的打包工具,可以创建自定义格式的分发包,包括jar、zip、tar等。

对于Spring Boot应用,它可以创建包含所有依赖的可执行jar,类似于Shade插件,但配置方式和功能有所不同。

Assembly插件更注重于创建完整的分发包,可以包含配置文件、启动脚本等。

3.2 配置方式

pom.xml文件中添加Assembly插件:

xml 复制代码
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>3.3.0</version>
            <configuration>
                <archive>
                    <manifest>
                        <mainClass>com.example.myapp.Application</mainClass>
                    </manifest>
                </archive>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

执行打包命令:

go 复制代码
mvn clean package

3.3 自定义Assembly描述符

对于更复杂的打包需求,可以创建自定义的Assembly描述符文件:

创建src/assembly/dist.xml文件:

xml 复制代码
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd">
    <id>distribution</id>
    <formats>
        <format>zip</format>
    </formats>
    <includeBaseDirectory>true</includeBaseDirectory>
    
    <fileSets>
        <!-- 应用jar文件 -->
        <fileSet>
            <directory>${project.build.directory}</directory>
            <outputDirectory>lib</outputDirectory>
            <includes>
                <include>${project.build.finalName}.jar</include>
            </includes>
        </fileSet>
        <!-- 配置文件 -->
        <fileSet>
            <directory>src/main/resources</directory>
            <outputDirectory>config</outputDirectory>
            <includes>
                <include>application*.yml</include>
                <include>application*.properties</include>
                <include>logback*.xml</include>
            </includes>
        </fileSet>
        <!-- 启动脚本 -->
        <fileSet>
            <directory>src/main/scripts</directory>
            <outputDirectory>bin</outputDirectory>
            <includes>
                <include>*.sh</include>
                <include>*.bat</include>
            </includes>
            <fileMode>0755</fileMode>
        </fileSet>
    </fileSets>
    
    <!-- 所有依赖 -->
    <dependencySets>
        <dependencySet>
            <outputDirectory>lib</outputDirectory>
            <excludes>
                <exclude>${project.groupId}:${project.artifactId}</exclude>
            </excludes>
        </dependencySet>
    </dependencySets>
</assembly>

然后在pom.xml中引用该描述符:

xml 复制代码
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>3.3.0</version>
    <configuration>
        <descriptors>
            <descriptor>src/assembly/dist.xml</descriptor>
        </descriptors>
    </configuration>
    <executions>
        <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>

3.4 创建可执行jar

如果需要创建可执行jar,需要添加maven-jar-plugin配置主类:

xml 复制代码
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>3.2.0</version>
    <configuration>
        <archive>
            <manifest>
                <addClasspath>true</addClasspath>
                <classpathPrefix>lib/</classpathPrefix>
                <mainClass>com.example.myapp.Application</mainClass>
            </manifest>
        </archive>
    </configuration>
</plugin>

3.5 打包结构分析

使用默认jar-with-dependencies描述符创建的Fat Jar结构:

erlang 复制代码
myapp-jar-with-dependencies.jar
├── META-INF
│   ├── MANIFEST.MF
│   └── maven
│       └── ...
├── com
│   └── example
│       └── myapp
│           └── ...
├── org
│   └── springframework
│       └── ...
└── ...

使用自定义描述符创建的分发包结构:

python 复制代码
myapp-distribution.zip
├── bin
│   ├── start.sh
│   └── start.bat
├── config
│   ├── application.yml
│   └── logback.xml
└── lib
    ├── myapp.jar
    ├── dependency1.jar
    ├── dependency2.jar
    └── ...

3.6 运行方式

对于使用jar-with-dependencies创建的可执行jar:

csharp 复制代码
java -jar myapp-jar-with-dependencies.jar

对于自定义分发包,使用提供的启动脚本:

bash 复制代码
cd myapp-distribution
./bin/start.sh

或手动启动:

bash 复制代码
cd myapp-distribution
java -cp "lib/*" com.example.myapp.Application

3.7 优缺点分析

优点:

  • 高度灵活,支持自定义分发包格式
  • 可以包含配置文件、启动脚本等
  • 支持多种打包格式(jar, zip, tar等)
  • 可以为不同环境创建不同的分发包
  • 适合创建完整的应用分发包

缺点:

  • 配置相对复杂
  • 对于简单应用,配置过于繁琐
  • 需要额外处理类路径和资源文件

3.8 适用场景

  • 需要完整分发包的企业应用
  • 包含多个模块的复杂应用
  • 需要包含配置文件和启动脚本的场景
  • 需要为不同环境创建不同分发包的场景
  • 有特殊分发需求的项目

四、总结

合理的打包策略能显著提升开发和部署效率,是应用成功运行的重要环节。

通过选择合适的打包方式,可以减少环境差异带来的问题,提升应用的可移植性和可维护性。

相关推荐
松仔log1 小时前
JetPack——Paging3+Room
android·java·zoom
㳺三才人子6 小时前
初探 Flask
后端·python·flask·html
星栈独行6 小时前
我在 Rust 全栈项目里用 JWT 做无状态认证
开发语言·后端·rust·前端框架·开源·github·web
Lei活在当下6 小时前
先用起来,再理解,关于协程Coroutine应该知道的事
android·java·jvm
Java爱好狂.6 小时前
Java程序员体系化学习路线(2026最新版)
java·后端·java面试·java架构师·java程序员·java八股文·java学习路线
陈随易7 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
tongluowan0077 小时前
以ReentrantLock为例解释AQS的工作流程
java·模板方法模式·aqs·reentrantlock
装不满的克莱因瓶7 小时前
SpringBoot 如何将 lib 目录中jar包打包进最终的jar包里面
spring boot·后端·maven·jar·mvn
ltl8 小时前
Transformer 原论文实验结果:为什么 28.4 BLEU 足以改写路线图
后端
身如柳絮随风扬8 小时前
Java 项目打包与部署完全指南:JAR vs WAR,从构建到运行
java·firefox·jar