在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 适用场景
- 需要完整分发包的企业应用
- 包含多个模块的复杂应用
- 需要包含配置文件和启动脚本的场景
- 需要为不同环境创建不同分发包的场景
- 有特殊分发需求的项目
四、总结
合理的打包策略能显著提升开发和部署效率,是应用成功运行的重要环节。
通过选择合适的打包方式,可以减少环境差异带来的问题,提升应用的可移植性和可维护性。