【包教包会】SpringBoot依赖Jar指定位置打包:配置+原理+避坑全解析

做后端开发的同学应该都遇到过这种情况:用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/*)。

这种默认方式的问题很明显:

  1. 依赖和项目代码混在一起,包体积大,传输和部署慢;
  2. 替换某个依赖需要重新打包,运维效率低;
  3. 无法实现依赖共享(多个项目共用同一批依赖,避免重复部署)。

而我们要做的"指定位置打包",核心就是通过配置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的配置,重点添加layoutincludesoutputDirectory等参数:

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>
关键参数解析(必看!避免配置错)
  1. <layout>ZIP</layout>:这是实现依赖分离的核心参数!默认值是JAR,会强制把所有依赖打包进BOOT-INF/lib;设置为ZIP后,插件会生成"松散格式"的打包结构,支持依赖放在外部指定目录。
  2. <outputDirectory>${project.build.directory}/lib</outputDirectory>:指定依赖Jar的输出目录,${project.build.directory}是Maven的内置变量,对应项目的target目录,所以最终依赖会打到target/lib下。
  3. <includes>:指定需要打包进项目自身Jar的内容,这里只包含当前项目的groupId和artifactId,意思是"只打包项目自己的代码,不打包任何依赖"。
  4. maven-dependency-plugin:辅助插件,负责把所有依赖复制到指定的lib目录,和spring-boot-maven-plugin配合使用,避免出现依赖复制不完整的问题(可选,但新手建议加上,更稳妥)。
步骤2:执行打包命令,验证结果
  1. 打开IDEA的Terminal终端,输入打包命令:

    bash 复制代码
    mvn clean package -Dmaven.test.skip=true

    (-Dmaven.test.skip=true表示跳过测试,加快打包速度)

  2. 打包成功后,进入项目的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

启动成功的判断标准:

  1. 控制台没有出现ClassNotFoundException(依赖找不到)异常;
  2. 项目接口能正常访问(比如访问之前写的/hello接口,能正常返回结果)。

三、 进阶配置:多模块项目如何指定依赖位置?

实际开发中,我们更多用的是多模块项目(比如parent模块+子模块:api、service、common),这种场景下打包需要注意两个点:一是只打包"启动模块"(比如api模块)的代码,二是确保所有子模块的依赖都能正确打到指定lib目录。

假设我们的多模块项目结构如下:

复制代码
parent-project/  // 父模块(pom类型)
├── common/      // 公共子模块(jar类型)
├── service/     // 业务子模块(jar类型)
└── api/         // 启动子模块(jar类型,包含SpringBoot启动类)
核心配置步骤(重点在启动模块api的pom.xml)
  1. 父模块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>
  2. 启动模块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>
  3. 打包与启动验证:

    • 进入父模块目录,执行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",找不到依赖
  • 坑因:
    1. -Dloader.path参数指定的路径错误(比如lib目录路径写反了);
    2. 依赖复制时遗漏了某些核心依赖(比如没加maven-dependency-plugin辅助复制);
  • 解决方案:
    1. 检查启动命令中的loader.path是否和实际lib目录路径一致(相对路径要注意当前命令执行目录);
    2. 打开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启动。

六、 实战延伸:依赖分离部署的优势与注意事项

学会指定位置打包后,实际部署时可以采用"依赖分离部署"的方案,这在生产环境中非常实用,优势很明显:

  1. 首次部署时,把lib目录上传到服务器后,后续更新项目只需要上传体积很小的app.jar,传输速度快,部署效率高;
  2. 多个项目共用同一批公共依赖(比如Spring、MyBatis等),可以把公共依赖放到服务器的共享目录,减少重复存储;
  3. 替换某个依赖时,直接在服务器上替换lib目录下对应的Jar包,不用重新打包项目,运维更灵活。

注意事项:

  1. 服务器上的lib目录路径要固定,启动命令中的loader.path要和实际路径一致;
  2. 多个项目共用依赖时,要确保依赖版本兼容(比如都是SpringBoot 3.2.x对应的依赖),避免版本冲突;
  3. 部署时要备份lib目录,避免替换依赖时误删或替换错误版本。

七、 总结:核心配置清单(包教包会精华)

最后把所有核心配置整理成清单,新手可以直接复制粘贴修改,快速实现指定位置打包:

  1. 基础配置(依赖打到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>
  2. 启动命令:java -jar -Dloader.path=./lib app.jar

只要记住核心逻辑------通过layout=ZIP开启依赖分离,通过outputDirectory指定依赖路径,通过loader.path告诉SpringBoot依赖位置,不管是基础场景还是多模块、自定义拆分场景,都能灵活适配。

希望这篇包教包会的教程能帮你彻底搞定SpringBoot依赖指定位置打包的问题。如果觉得有用,欢迎点赞、收藏、关注,后续还会分享更多SpringBoot实战打包和部署的干货!

相关推荐
FAFU_kyp1 小时前
Rust 泛型(Generics)学习教程
开发语言·学习·rust
VekiSon1 小时前
ARM架构——C 语言+SDK+BSP 实现 LED 点灯与蜂鸣器驱动
c语言·开发语言·arm开发·嵌入式硬件
研☆香1 小时前
JavaScript 历史列表查询的方法
开发语言·javascript·ecmascript
小二·1 小时前
Python Web 开发进阶实战:可持续计算 —— 在 Flask + Vue 中构建碳感知应用(Carbon-Aware Computing)
前端·python·flask
Elnaij1 小时前
从C++开始的编程生活(18)——二叉搜索树基础
开发语言·c++
小饼干超人1 小时前
如何兼容不同版本的 scikit-learn(sklearn)库,统一获取“均方根误差(RMSE)”的计算函数
python·scikit-learn·sklearn
a程序小傲2 小时前
中国邮政Java面试被问:边缘计算的数据同步和计算卸载
java·服务器·开发语言·算法·面试·职场和发展·边缘计算
Java程序员威哥2 小时前
Java微服务可观测性实战:Prometheus+Grafana+SkyWalking全链路监控落地
java·开发语言·python·docker·微服务·grafana·prometheus
全栈软件开发2 小时前
PHP实时消息聊天室源码 PHP+WebSocket
开发语言·websocket·php