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 适用场景

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

四、总结

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

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

相关推荐
编程乐学(Arfan开发工程师)3 小时前
56、原生组件注入-原生注解与Spring方式注入
java·前端·后端·spring·tensorflow·bug·lua
周某某~4 小时前
七.适配器模式
java·设计模式·适配器模式
Elcker5 小时前
Springboot+idea热更新
spring boot·后端·intellij-idea
奔跑的小十一5 小时前
JDBC接口开发指南
java·数据库
刘大猫.6 小时前
业务:资产管理功能
java·资产管理·资产·资产统计·fau·bpb·mcb
YuTaoShao6 小时前
Java八股文——JVM「内存模型篇」
java·开发语言·jvm
开开心心就好6 小时前
电脑扩展屏幕工具
java·开发语言·前端·电脑·php·excel·batch
南玖yy7 小时前
深入理解 x86 汇编中的符号扩展指令:从 CBW 到 CDQ 的全解析
开发语言·汇编·arm开发·后端·架构·策略模式