做后端开发的同学应该都遇到过这种情况:用SpringBoot默认打包方式打出的Jar包,所有依赖都混在BOOT-INF/lib目录下,不仅包体积庞大,部署时想替换某个依赖都得重新打包;更麻烦的是如果要做依赖分离部署(比如把公共依赖放在服务器共享目录),默认打包方式完全满足不了需求。
我刚接触SpringBoot打包时,也被依赖存放问题折腾过------想把依赖打到项目根目录的lib文件夹,查了一堆教程要么配置不全,要么没讲清原理,最后还是靠啃Maven插件文档才搞定。今天就把这份"踩坑总结"整理成包教包会的教程,从核心原理、基础配置、多模块适配、避坑指南 到部署验证,一步步带你实现SpringBoot依赖Jar指定位置打包,新手也能跟着操作一次成功。
一、 先搞懂:SpringBoot默认打包为什么依赖都在BOOT-INF/lib?
在讲指定位置打包前,先得明白SpringBoot默认的打包逻辑------这是理解后续配置的关键,不然配置参数改了都不知道为啥生效。
我们平时用的SpringBoot打包核心是spring-boot-maven-plugin插件,这个插件本质是在Maven默认打包的基础上,做了一层封装,生成符合SpringBoot运行规范的"可执行Jar包"(也叫-fatjar)。
默认打包流程里,插件会把项目编译后的class文件、配置文件放到BOOT-INF/classes目录,把所有依赖的Jar包(包括项目依赖、第三方依赖)统一拷贝到BOOT-INF/lib目录,同时在Jar包根目录生成META-INF/MANIFEST.MF文件,指定项目的主类和依赖加载路径(默认是BOOT-INF/classes/和BOOT-INF/lib/*)。
这种默认方式的问题很明显:
- 依赖和项目代码混在一起,包体积大,传输和部署慢;
- 替换某个依赖需要重新打包,运维效率低;
- 无法实现依赖共享(多个项目共用同一批依赖,避免重复部署)。
而我们要做的"指定位置打包",核心就是通过配置spring-boot-maven-plugin插件,改变依赖Jar的拷贝路径,同时修改MANIFEST.MF里的依赖加载路径,让SpringBoot能找到指定位置的依赖。
二、 基础配置:把依赖打到项目根目录的lib文件夹(最常用场景)
先从最简单的场景入手:将所有依赖Jar打到项目根目录的lib目录下,项目自身代码打成独立的Jar包(比如叫app.jar),最终目录结构如下:
project/
├── app.jar // 项目自身代码的Jar包
└── lib/ // 所有依赖Jar包
├── spring-web-6.1.2.jar
├── mybatis-3.5.15.jar
└── ...(其他依赖)
步骤1:核心配置spring-boot-maven-plugin插件
打开项目根目录的pom.xml,修改spring-boot-maven-plugin的配置,重点添加layout、includes、outputDirectory等参数:
xml
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.2.0</version> <!-- 和SpringBoot版本保持一致 -->
<configuration>
<!-- 1. 关键配置:指定打包类型为ZIP(非默认的JAR),支持依赖分离 -->
<layout>ZIP</layout>
<!-- 2. 指定项目自身代码打包后的Jar名称(可选,默认是项目名+版本) -->
<finalName>app</finalName>
<!-- 3. 配置依赖输出目录:将依赖打到项目根目录的lib文件夹 -->
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<!-- 4. 配置项目自身代码的打包范围:只打包项目自身的class和配置,排除所有依赖 -->
<includes>
<include>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
</include>
</includes>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal> <!-- 重打包goal,必须添加 -->
</goals>
</execution>
</executions>
</plugin>
<!-- 可选:如果需要单独复制依赖(避免插件配置遗漏),可添加maven-dependency-plugin辅助 -->
<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>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!-- 依赖输出目录:和上面spring-boot-maven-plugin保持一致 -->
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<!-- 排除测试依赖 -->
<excludeScope>test</excludeScope>
<!-- 排除系统依赖(如果有的话) -->
<excludeSystemScope>true</excludeSystemScope>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
关键参数解析(必看!避免配置错)
<layout>ZIP</layout>:这是实现依赖分离的核心参数!默认值是JAR,会强制把所有依赖打包进BOOT-INF/lib;设置为ZIP后,插件会生成"松散格式"的打包结构,支持依赖放在外部指定目录。<outputDirectory>${project.build.directory}/lib</outputDirectory>:指定依赖Jar的输出目录,${project.build.directory}是Maven的内置变量,对应项目的target目录,所以最终依赖会打到target/lib下。<includes>:指定需要打包进项目自身Jar的内容,这里只包含当前项目的groupId和artifactId,意思是"只打包项目自己的代码,不打包任何依赖"。maven-dependency-plugin:辅助插件,负责把所有依赖复制到指定的lib目录,和spring-boot-maven-plugin配合使用,避免出现依赖复制不完整的问题(可选,但新手建议加上,更稳妥)。
步骤2:执行打包命令,验证结果
-
打开IDEA的Terminal终端,输入打包命令:
bashmvn clean package -Dmaven.test.skip=true(-Dmaven.test.skip=true表示跳过测试,加快打包速度)
-
打包成功后,进入项目的target目录,会看到两个关键内容:
- app.jar:项目自身的Jar包(体积很小,只有代码和配置);
- lib目录:里面是所有依赖的Jar包(比如spring-web、mybatis等)。
步骤3:启动项目,验证依赖是否生效
指定依赖位置后,启动项目时需要通过-Dloader.path参数告诉SpringBoot依赖的存放路径,启动命令如下:
bash
# 方式1:在命令行指定依赖路径(lib目录和app.jar同级)
java -jar -Dloader.path=./lib app.jar
# 方式2:如果lib目录在其他路径,写绝对路径
java -jar -Dloader.path=/usr/local/project/lib app.jar
启动成功的判断标准:
- 控制台没有出现
ClassNotFoundException(依赖找不到)异常; - 项目接口能正常访问(比如访问之前写的/hello接口,能正常返回结果)。
三、 进阶配置:多模块项目如何指定依赖位置?
实际开发中,我们更多用的是多模块项目(比如parent模块+子模块:api、service、common),这种场景下打包需要注意两个点:一是只打包"启动模块"(比如api模块)的代码,二是确保所有子模块的依赖都能正确打到指定lib目录。
假设我们的多模块项目结构如下:
parent-project/ // 父模块(pom类型)
├── common/ // 公共子模块(jar类型)
├── service/ // 业务子模块(jar类型)
└── api/ // 启动子模块(jar类型,包含SpringBoot启动类)
核心配置步骤(重点在启动模块api的pom.xml)
-
父模块pom.xml:统一管理
spring-boot-maven-plugin版本(避免子模块版本不一致)xml<build> <pluginManagement> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>3.2.0</version> </plugin> </plugins> </pluginManagement> </build> -
启动模块api的pom.xml:配置打包插件,指定依赖输出目录
xml<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <layout>ZIP</layout> <finalName>api-app</finalName> <!-- 依赖输出到target/lib目录 --> <outputDirectory>${project.build.directory}/lib</outputDirectory> <!-- 只打包api模块自身的代码,排除其他依赖(包括common、service子模块) --> <includes> <include> <groupId>com.example</groupId> <!-- api模块的groupId --> <artifactId>api</artifactId> <!-- api模块的artifactId --> </include> </includes> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> <!-- 辅助复制依赖:确保common、service子模块的依赖也能打到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> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/lib</outputDirectory> <excludeScope>test</excludeScope> <excludeSystemScope>true</excludeSystemScope> <!-- 可选:排除不需要的依赖(比如本地测试依赖) --> <excludes> <exclude> <groupId>junit</groupId> <artifactId>junit</artifactId> </exclude> </excludes> </configuration> </execution> </executions> </plugin> </plugins> </build> -
打包与启动验证:
- 进入父模块目录,执行
mvn clean package -Dmaven.test.skip=true; - 打包成功后,进入api模块的target目录,会看到api-app.jar和lib目录(包含common、service子模块以及所有第三方依赖);
- 启动命令:
java -jar -Dloader.path=./lib api-app.jar,验证项目是否正常运行。
- 进入父模块目录,执行
四、 高级需求:自定义依赖存放路径+排除/包含指定依赖
实际开发中可能会有更灵活的需求,比如"把公共依赖打到lib/common目录,把业务依赖打到lib/business目录",或者"排除某个不需要打包的依赖",这些都可以通过调整插件配置实现。
需求1:按依赖类型拆分存放目录
比如把Spring相关的公共依赖放到lib/spring目录,其他业务依赖放到lib/business目录,配置如下(核心用maven-dependency-plugin的分类复制功能):
xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.0</version>
<executions>
<!-- 复制Spring相关依赖到lib/spring -->
<execution>
<id>copy-spring-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib/spring</outputDirectory>
<includeGroupIds>org.springframework</includeGroupIds> <!-- 只包含Spring相关groupId -->
</configuration>
</execution>
<!-- 复制其他业务依赖到lib/business -->
<execution>
<id>copy-business-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib/business</outputDirectory>
<excludeGroupIds>org.springframework</excludeGroupIds> <!-- 排除Spring相关依赖 -->
</configuration>
</execution>
</executions>
</plugin>
启动时需要指定所有依赖目录:java -jar -Dloader.path=./lib/spring,./lib/business api-app.jar
需求2:排除/包含指定依赖
比如项目中有个本地测试用的依赖(比如mockito-core),打包时不想打到lib目录,或者只想包含某个特定版本的依赖,配置如下:
xml
<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>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<!-- 排除指定依赖:groupId+artifactId -->
<excludes>
<exclude>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</exclude>
<!-- 也可以按版本排除 -->
<exclude>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.80</version>
</exclude>
</excludes>
<!-- 只包含指定依赖(和excludes互斥,选一个用) -->
<!-- <includes>
<include>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</include>
</includes> -->
</configuration>
</execution>
</executions>
</plugin>
五、 包教包会避坑指南:5个新手必踩坑及解决方案
我整理了自己和同事踩过的5个高频坑,每个坑都标注了"坑因"和"解决方案",跟着操作时一定要注意!
坑1:配置后依赖没打到指定目录,还是在BOOT-INF/lib
- 坑因:没加
<layout>ZIP</layout>参数,或者参数值写错(比如写成了JAR); - 解决方案:确保
spring-boot-maven-plugin的configuration中添加<layout>ZIP</layout>,这是依赖分离的核心参数,不能少。
坑2:启动时提示"ClassNotFoundException",找不到依赖
- 坑因:
-Dloader.path参数指定的路径错误(比如lib目录路径写反了);- 依赖复制时遗漏了某些核心依赖(比如没加
maven-dependency-plugin辅助复制);
- 解决方案:
- 检查启动命令中的
loader.path是否和实际lib目录路径一致(相对路径要注意当前命令执行目录); - 打开lib目录,确认缺失的依赖是否存在,不存在的话重新执行
mvn clean package,查看打包日志是否有依赖复制失败的信息。
- 检查启动命令中的
坑3:多模块项目打包后,子模块依赖没打到lib目录
- 坑因:在父模块配置了打包插件,而不是在启动模块(api模块)配置;
- 解决方案:多模块打包的插件配置必须写在启动模块的pom.xml中,父模块只需要通过pluginManagement统一管理插件版本即可。
坑4:打包后项目自身的class文件丢失,启动提示"找不到主类"
- 坑因:
<includes>参数配置错误,比如groupId或artifactId写错,导致没把项目自身代码打包进app.jar; - 解决方案:检查
<includes>中的groupId和artifactId是否和当前模块的pom.xml一致,比如api模块的includes就应该是api模块的groupId和artifactId。
坑5:依赖打到指定目录后,Jar包双击无法运行
- 坑因:SpringBoot可执行Jar包双击运行时,无法自动识别
loader.path参数,只能通过命令行指定依赖路径; - 解决方案:双击运行只适合默认打包的Jar包(依赖在BOOT-INF/lib),指定位置存放依赖的项目,必须通过命令行
java -jar -Dloader.path=./lib app.jar启动。
六、 实战延伸:依赖分离部署的优势与注意事项
学会指定位置打包后,实际部署时可以采用"依赖分离部署"的方案,这在生产环境中非常实用,优势很明显:
- 首次部署时,把lib目录上传到服务器后,后续更新项目只需要上传体积很小的app.jar,传输速度快,部署效率高;
- 多个项目共用同一批公共依赖(比如Spring、MyBatis等),可以把公共依赖放到服务器的共享目录,减少重复存储;
- 替换某个依赖时,直接在服务器上替换lib目录下对应的Jar包,不用重新打包项目,运维更灵活。
注意事项:
- 服务器上的lib目录路径要固定,启动命令中的
loader.path要和实际路径一致; - 多个项目共用依赖时,要确保依赖版本兼容(比如都是SpringBoot 3.2.x对应的依赖),避免版本冲突;
- 部署时要备份lib目录,避免替换依赖时误删或替换错误版本。
七、 总结:核心配置清单(包教包会精华)
最后把所有核心配置整理成清单,新手可以直接复制粘贴修改,快速实现指定位置打包:
-
基础配置(依赖打到target/lib,项目自身为app.jar):
xml<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>3.2.0</version> <configuration> <layout>ZIP</layout> <finalName>app</finalName> <outputDirectory>${project.build.directory}/lib</outputDirectory> <includes> <include> <groupId>${project.groupId}</groupId> <artifactId>${project.artifactId}</artifactId> </include> </includes> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> <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> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/lib</outputDirectory> <excludeScope>test</excludeScope> </configuration> </execution> </executions> </plugin> -
启动命令:
java -jar -Dloader.path=./lib app.jar
只要记住核心逻辑------通过layout=ZIP开启依赖分离,通过outputDirectory指定依赖路径,通过loader.path告诉SpringBoot依赖位置,不管是基础场景还是多模块、自定义拆分场景,都能灵活适配。
希望这篇包教包会的教程能帮你彻底搞定SpringBoot依赖指定位置打包的问题。如果觉得有用,欢迎点赞、收藏、关注,后续还会分享更多SpringBoot实战打包和部署的干货!