Maven 依赖分离
Maven 依赖分离是指将项目的**核心代码(主 Jar)与第三方依赖库(依赖 Jar)**分离到不同目录,而非打包在同一个文件中。这种方式常用于优化部署、减少重复传输、或配合 Docker 镜像分层构建。
以下是两种最常用的实现方案:
方案一:使用 maven-dependency-plugin(通用方案)
适用于所有 Maven 项目,通过插件将依赖复制到指定目录,并配置主 Jar 的 Class-Path 指向该目录。
1. 配置 pom.xml
在 <build><plugins> 中添加以下两个插件:
- 复制依赖到 target/lib 目录
- 配置主 Jar 的 Class-Path 和 Main-Class
xml
<project>
<!-- ... 其他配置 ... -->
<build>
<plugins>
<!-- 1. 复制依赖到 target/lib 目录 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase> <!-- 绑定到 package 阶段 -->
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!-- 依赖输出目录 -->
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<!-- 是否包含传递性依赖 -->
<includeScope>runtime</includeScope>
<!-- 保持依赖的原有文件名 -->
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
<!-- 2. 配置主 Jar 的 Class-Path 和 Main-Class -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<!-- 程序入口类 -->
<mainClass>com.example.YourMainClass</mainClass>
<!-- 自动添加 Class-Path -->
<addClasspath>true</addClasspath>
<!-- Class-Path 中依赖的前缀目录 -->
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
** 关键配置说明 **
| 配置项 | 作用 |
|---|---|
<includeScope>runtime</includeScope> |
仅复制运行时需要的依赖(compile 和 runtime 范围),排除 test(测试用)和 provided(容器提供)的依赖。 |
<classpathPrefix>lib/</classpathPrefix> |
告诉主 Jar 去 lib/ 目录下找依赖,必须与 outputDirectory 的目录名一致。 |
<useBaseVersion>true</useBaseVersion> |
避免 SNAPSHOT 版本的依赖文件名带时间戳(如 1.0-20240101.123456-1.jar),保持文件名简洁。 |
2. 执行打包
运行 mvn clean package,你会在 target 目录下看到:
your-app.jar(你的主程序,体积很小)lib/(包含所有依赖的 Jar 包)
3. 运行方式
将 your-app.jar 和 lib/ 放在同一目录下,执行:
bash
java -jar your-app.jar
方案二:Spring Boot 项目专用(spring-boot-maven-plugin)
如果是 Spring Boot 项目,默认插件会把所有依赖打进一个 Fat Jar。若需分离,需调整插件配置。
1. 配置 pom.xml
xml
<project>
<!-- ... 其他配置 ... -->
<build>
<plugins>
<!-- Spring Boot 插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- 1. 启用分层打包(Layered Jars) -->
<layers>
<enabled>true</enabled>
</layers>
<!-- 或者 2. 使用 Properties 配置(旧版本方式) -->
<!-- <requiresUnpack>
<dependency>
<groupId>com.example</groupId>
<artifactId>some-large-lib</artifactId>
</dependency>
</requiresUnpack> -->
</configuration>
</plugin>
<!-- 配合复制依赖插件(可选,若需物理分离) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2. Spring Boot 分层 Jar 说明
Spring Boot 的 layers 模式虽然不直接物理分离文件,但内部将 Jar 分为了不同层(如 dependencies、spring-boot-loader、snapshot-dependencies、application),非常适合 Docker 镜像构建,能利用缓存加速。
如果需要物理分离 (像方案一那样 lib 目录在外),建议直接使用 方案一 的通用配置,Spring Boot 项目同样兼容。
总结
- 普通项目 :推荐 方案一,结构清晰,运行简单。
- Spring Boot + Docker :推荐使用
layers分层模式,优化镜像构建速度。 - 注意 :分离后部署时,务必保证
lib目录和主 Jar 的相对位置与classpathPrefix配置一致。
maven-dependency-plugin 介绍
maven-dependency-plugin 是 Maven 中处理依赖最核心的插件,提供了复制、解压、解析、分析项目依赖等功能,是实现依赖分离、离线构建、依赖冲突排查的关键工具。
以下是该插件的全面配置详解 ,结合核心 Goal(目标)和实际场景示例说明。
一、插件基本信息
在 pom.xml 中引入插件(建议使用最新稳定版):
xml
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version> <!-- 请使用最新版本 -->
</plugin>
</plugins>
</build>
二、核心 Goal 及详细配置
该插件通过不同的 Goal(目标)实现不同功能,以下是最常用的 4 个 Goal:
1. dependency:copy-dependencies(最常用:复制依赖到指定目录)
作用:将项目的依赖(包括传递性依赖)从本地仓库复制到指定目录,是实现"依赖分离"的核心 Goal。
常用配置参数
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
outputDirectory |
String |
${project.build.directory}/dependency |
依赖复制的目标目录(如 target/lib) |
includeScope |
String |
- | 仅包含指定范围的依赖(如 runtime、compile、test) |
excludeScope |
String |
- | 排除指定范围的依赖 |
includeGroupIds |
String |
- | 仅包含指定 GroupId 的依赖(多个用逗号分隔) |
excludeGroupIds |
String |
- | 排除指定 GroupId 的依赖 |
includeArtifactIds |
String |
- | 仅包含指定 ArtifactId 的依赖 |
excludeArtifactIds |
String |
- | 排除指定 ArtifactId 的依赖 |
includeTypes |
String |
jar |
仅包含指定类型的依赖(如 jar、war、pom) |
classifier |
String |
- | 仅包含指定 classifier 的依赖(如 sources、javadoc) |
overWriteReleases |
boolean |
false |
是否覆盖已存在的 Release 版本依赖 |
overWriteSnapshots |
boolean |
true |
是否覆盖已存在的 Snapshot 版本依赖 |
overWriteIfNewer |
boolean |
true |
仅当依赖文件更新时才覆盖 |
useBaseVersion |
boolean |
false |
文件名使用基础版本(如 1.0 而非 1.0-20240101.123456-1) |
stripVersion |
boolean |
false |
复制的文件名去掉版本号(如 commons-lang3.jar) |
prependGroupId |
boolean |
false |
文件名前加上 GroupId 前缀(如 org.apache.commons-commons-lang3.jar) |
copyPom |
boolean |
false |
是否同时复制依赖的 .pom 文件 |
failOnMissingClassifierArtifact |
boolean |
true |
当找不到指定 classifier 的依赖时是否构建失败 |
完整配置示例(依赖分离场景)
xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<id>copy-libs</id>
<phase>package</phase> <!-- 绑定到 package 阶段 -->
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!-- 1. 目标目录:target/lib -->
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<!-- 2. 仅包含 runtime 范围的依赖(排除 test、provided) -->
<includeScope>runtime</includeScope>
<!-- 3. 排除不需要的依赖(如 lombok) -->
<excludeGroupIds>org.projectlombok</excludeGroupIds>
<!-- 4. 优化文件覆盖策略 -->
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
<!-- 5. 文件名使用基础版本(避免 SNAPSHOT 时间戳) -->
<useBaseVersion>true</useBaseVersion>
</configuration>
</execution>
</executions>
</plugin>
2. dependency:unpack(解压依赖)
作用:将依赖的 Jar/War 包解压到指定目录(常用于提取依赖中的资源文件、 native 库等)。
特有配置参数(除了 copy-dependencies 的参数外)
| 参数名 | 类型 | 说明 |
|---|---|---|
includes |
String |
仅解压指定文件(Ant 风格路径,如 **/*.xml) |
excludes |
String |
排除指定文件 |
配置示例(提取依赖中的配置文件)
xml
<execution>
<id>unpack-configs</id>
<phase>generate-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/classes/config</outputDirectory>
<!-- 仅解压某个特定依赖 -->
<includeGroupIds>com.example</includeGroupIds>
<includeArtifactIds>my-common-lib</includeArtifactIds>
<!-- 仅解压 xml 和 properties 文件 -->
<includes>**/*.xml,**/*.properties</includes>
</configuration>
</execution>
3. dependency:resolve(解析依赖)
作用:确保所有依赖都下载到本地仓库,不做复制/解压操作,常用于构建前的预检查或离线构建准备。
配置示例
xml
<execution>
<id>resolve-deps</id>
<phase>validate</phase>
<goals>
<goal>resolve</goal>
</goals>
<configuration>
<!-- 解析所有范围的依赖(包括 test) -->
<includeScope>test</includeScope>
</configuration>
</execution>
4. dependency:analyze(分析依赖)
作用:分析项目的依赖使用情况,找出:
unused declared dependencies:声明了但未使用的依赖used undeclared dependencies:使用了但未直接声明的依赖(传递性依赖)
常用配置参数
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
failOnWarning |
boolean |
false |
发现上述问题时是否构建失败 |
ignoreNonCompile |
boolean |
false |
忽略 test、provided、runtime 范围的依赖 |
usedDependencies |
String |
- | 强制声明某些依赖是"已使用"的(避免误报) |
配置示例(构建时检查未使用依赖)
xml
<execution>
<id>analyze-deps</id>
<phase>verify</phase>
<goals>
<goal>analyze-only</goal> <!-- 注意:用 analyze-only 而非 analyze,避免重复执行 -->
</goals>
<configuration>
<!-- 发现未使用依赖时构建失败(强制清理无用依赖) -->
<failOnWarning>true</failOnWarning>
<!-- 忽略 test 范围 -->
<ignoreNonCompile>true</ignoreNonCompile>
<!-- 强制认为 lombok 是已使用的(避免误报) -->
<usedDependencies>
<usedDependency>org.projectlombok:lombok</usedDependency>
</usedDependencies>
</configuration>
</execution>
三、高级应用场景
1. 离线构建准备(dependency:go-offline)
如果需要在无网络环境下构建项目,可先执行:
bash
mvn dependency:go-offline
该命令会下载所有依赖(包括插件及其依赖)到本地仓库,配合 -o(offline)参数即可离线构建:
bash
mvn clean package -o
2. 结合 Profile 实现环境差异化依赖
xml
<profiles>
<profile>
<id>prod</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-prod-libs</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/prod-lib</outputDirectory>
<!-- 生产环境排除 test 依赖 -->
<excludeScope>test</excludeScope>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
四、注意事项
- 版本兼容性:确保插件版本与 Maven 核心版本兼容(Maven 3.6+ 建议使用 3.2.0+ 版本的插件)。
- 传递性依赖 :
includeScope会同时影响传递性依赖(如includeScope=runtime会包含compile范围的传递性依赖,因为 Maven 会将其转为runtime)。 - 性能优化 :如果依赖数量很多,可通过
excludeGroupIds排除不需要的组(如org.apache.maven),提升复制速度。