
上个月发版临时环境一启动日志里赫然连的是测试库。排查了一圈最后拔开JAR包才发现application-dev.yml安安静静躺在BOOT-INF/classes里把生产配置全给覆盖了。这种问题在团队里经常以各种姿势出现打包前没有把不需要的文件处理干净Maven 把不该进产物的东西一起揉进了包里。今天这篇就围绕“Maven打包前先删除不需要的文件”这个场景把我用过、踩过、最后沉淀下来的方案一次性讲清楚。先说这篇文章适合谁。如果你每天用 IDEA 或命令行mvn clean package出 JAR、WAR发布前总担心包里有配置残留、测试数据、旧静态资源或者想在流水线里自动清理target里的杂七杂八那么下面的内容可以直接照着抄。我会从为什么要删、Maven 哪个阶段删最合适、三套常用删除方案、真实排错链路再到按环境动态清理的进阶玩法一步步展开其中有不少是文档里不会写、但实战一定会撞上的细节。1. 为什么打包前要动手删文件不是洁癖是发布产物里的地雷很多新人一开始不理解既然源码都在项目里进不进包不都一样的吗还真不一样。你本机编译调试是一套逻辑发布到服务器是另一套逻辑。包里的每一个文件最终都会被加载进运行环境任何一个多余文件都可能变成线上事故的导火索。1.1 一个真实的误发布事故我经历的那次事故细节是这样的。项目用 Spring Boot 多环境配置src/main/resources下有application.yml、application-dev.yml、application-prod.yml。本地开发时靠--spring.profiles.activedev启动一切都正常。发版时大家习惯性执行mvn package然后扔给运维部署。问题就出在application-dev.yml被一起打进了 JAR。服务器上的环境变量没有显式指定 prodSpring Boot 加载配置时默认激活的 profile 又用的 dev结果所有数据源连接直接指向了开发库。运维一开始不承认是包的问题直到我们把 JAR 解压把BOOT-INF/classes下的文件列出来才发现application-dev.yml和application-prod.yml一个不少全在包里。从那以后我给自己定了一条规矩发布产物里只能有当前环境必需的文件其他一切都要在打包前清掉。这不是强迫症是对运维和业务负责。1.2 哪些文件最容易混进包根据经验打包时出现频率最高的“多余文件”大概是这几类多环境配置文件尤其是不该进生产包的 dev、test 配置本地调试用的 SQL 脚本、初始化数据、Mock 数据设计稿、需求文档、临时图片被随手放进了resources目录前端构建出的旧版静态资源比如static目录里残留的过期 JS、CSS各种 IDE 生成的文件比如.DS_Store、Thumbs.db、*.swp体积很大的测试样本文件违反了构建产物应该精简的原则这些文件平时躺在源码目录里没感觉一旦进入 JAR/WAR轻则包体膨胀、启动变慢重则泄露信息、覆盖配置直接引发生产事故。2. 先把 Maven 的构建阶段和清理机制摸清楚删除动作该挂在哪个步骤刚开始我图省事直接在mvn clean package之后手动进target目录删文件。结果下次构建又出现了因为源码里的文件还在重新打包马上又会复制进去。后来才意识到正确做法是在 Maven 构建生命周期内、打包动作发生之前把文件删掉或者排除掉这样每次构建都能稳定复现结果。2.1 Maven 生命周期里的关键阶段Maven 的生命周期不是玄学是可以直接看见的顺序执行链条。默认生命周期里和打包直接相关的阶段大概是这样一条线validate process-resources compile process-classes prepare-package package verify install deploy注意看prepare-package这个阶段它的位置非常微妙此时代码已经编译完成资源文件已经复制进target/classes但还没有生成最终的 JAR/WAR 包。也就是说如果你想让“清理”动作刚好发生在打包之前prepare-package就是最理想的挂载点。再来看 clean 生命周期。通常情况下mvn clean执行的是 clean 生命周期里的 clean 阶段它的职责是删除target目录。默认的 clean 行为是“清空构建输出目录”但你完全可以让它在清理target的同时额外删除指定文件和目录。关键是搞清楚它什么时候执行、怎么触发。2.2 为什么单独用 mvn clean 删不掉源文件很多人在pom.xml里配置了maven-clean-plugin然后执行mvn clean package发现源文件还在目标包里。原因是默认的 clean 插件只负责清理target这类构建产物它不会去碰src/main/resources下的源码文件。如果你真想删除源文件或者某个中间目录需要额外配置filesets让 clean 插件明白“除了 target你还要负责处理这些路径”。这一点后面细说。在此之前先记住一个判断原则mvn clean删的是 target也就是构建输出。src下的文件是否进入最终产物由编译、资源复制和打包阶段决定。2.3 选择删除时机的两个原则我在实际项目里会选择绑定时机时始终坚持两条原则。一是越接近 package 越好。绑定到validate或process-resources太早很可能文件被删掉之后又被后续阶段重新复制出来。绑定到prepare-package最稳因为资源复制已经结束接下来就是打包插件读取target/classes和target/classes/META-INF内容的时候。二是尽量别修改源码目录。如果目标是发布 produto更推荐通过资源过滤、排除配置来达到“不打包”的效果而不是真的把源码文件删了。因为你本地开发、测试时可能还需要这些文件删了源文件还得靠 Git 找回麻烦不说还容易误操作。3. 三种实用删除方案clean 插件、antrun 插件与资源排除这一节是全文的操作核心。我会给出三种最常用的方案你根据项目实际情况选一种就好不推荐全都堆在一起。3.1 方案一用 maven-clean-plugin 在打包前删除指定文件先说maven-clean-plugin的进阶用法。如果你已经熟悉它默认的清理target的行为那配置filesets就是给它扩展“清除名单”的方式。plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-clean-plugin/artifactId version3.2.0/version executions execution idclean-unwanted-files/id phaseprepare-package/phase goals goalclean/goal /goals configuration filesets fileset directory${project.build.outputDirectory}/config/directory includes includeapplication-dev.yml/include includeapplication-test.yml/include /includes followSymlinksfalse/followSymlinks /fileset fileset directory${project.build.outputDirectory}/static/directory includes includeold-*.js/include includeold-*.css/include /includes /fileset /filesets /configuration /execution /executions /plugin这段配置的含义很直白执行到prepare-package阶段时调用 clean 插件的 clean goal在target/classes下指定目录里删除匹配规则的文件。这里有个细节我必须单独说执行mvn package时clean 插件默认不会运行只有主动绑定到某阶段的 execution 才会执行。而如果你同时执行mvn clean packageclean 生命周期里的 clean 会先把 target 清了再走到 prepare-package 时你绑定的这个执行又会清理这批文件。两次清理并不冲突第二次主要是为了保险。如果你不希望 clean 插件在删指定文件的同时还把整个 target 删掉可以加一个参数configuration excludeDefaultDirectoriestrue/excludeDefaultDirectories filesets !-- ... -- /filesets /configurationexcludeDefaultDirectories设置为 true 后插件就只删除你在 filesets 里指定的内容不碰默认的 target 目录。这种方式适合你已经把整个生命周期安排得很紧凑、不想让 clean 插件顺手清空一切的项目。3.2 方案二用 maven-antrun-plugin 执行删除任务maven-antrun-plugin是一个“万能胶水”插件它允许你在 Maven 生命周期任意阶段调用 Ant 的 task。删除文件这种动作用 Ant 的delete写起来非常直观而且支持文件、目录、通配符灵活性比 clean 插件还大。plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-antrun-plugin/artifactId version3.1.0/version executions execution idremove-files-before-package/id phaseprepare-package/phase goals goalrun/goal /goals configuration target delete file${project.build.outputDirectory}/application-dev.yml/ delete dir${project.build.outputDirectory}/test-data/ delete fileset dir${project.build.outputDirectory}/static includes**/*.map/ /delete /target /configuration /execution /executions /plugin这段配置比 clean 插件更好理解在prepare-package阶段删除target/classes下的单个文件、整个目录以及匹配*.map的源码映射文件。在实际项目中我经常用这个方法清理webpack构建后遗留的 sourcemap 文件。前后端分离项目里前端产物经常一股脑扔进resources/staticsourcemap 文件对线上排查没有直接帮助反而会暴露源码结构在打进 JAR 之前顺手删掉是最省事的。3.3 方案三用 maven-resources-plugin 从源头排除资源如果问题的本质是“有些资源文件根本不该进 classpath”那最优解不是在打包前删而是在资源复制阶段就直接排除它。maven-resources-plugin的excludes就是干这个的。build resources resource directorysrc/main/resources/directory filteringtrue/filtering excludes excludeapplication-dev.yml/exclude excludeapplication-test.yml/exclude excludedb/migration/local/**/exclude exclude**/*.psd/exclude exclude**/docs/**/exclude /excludes /resource /resources /build这段配置会告诉 Maven复制src/main/resources目录下的资源时凡是匹配这些规则的路径一律不复制。文件依然保留在源码目录里但永远不会进入target/classes自然也就不会出现在最终 JAR/WAR 里。这也是我最推荐的方式尤其是对于多环境配置的问题。开发环境本地启动仍然能读到application-dev.yml因为 IDE 直接读的是src/main/resources而 Maven 打包时食堂就把它筛掉了一举两得。3.4 三种方案的对比与选择逻辑用一个表格来对比这三套方案方便你决策方案适用场景优点注意点maven-clean-plugin删除 target 内残留文件、特定构建产物配置集中不引入新插件默认行为是清空目标目录需要留意 excludeDefaultDirectoriesmaven-antrun-plugin复杂删除逻辑、跨文件类型、需要通配符匹配灵活能写完整 Ant 脚本引入额外插件Ant 语法需要团队了解maven-resources-plugin从源头排除 src/main/resources 下的文件真正防止进入构建产物本地开发不受影响只适用于资源文件不适用于运行期生成的临时文件如果只是简单排除几个配置文件首选第三个方案如果要在打包前清理target下已经生成的各种中间文件用第一个方案更顺手如果删除逻辑非常复杂甚至要联动其他脚本那就上 antrun 插件。4. 排错实录配置了删除包里的文件为什么还在配置完插件后最让人崩溃的不是报错而是执行完mvn package解压 JAR 一看文件还在。我自己踩过三次这种坑每次原因都不一样整理成一条排错链路分享给你希望你少走弯路。4.1 第一次定位phase 绑定太早文件被后续阶段复制回来最开始我把 clean 插件绑定在process-resources上因为直觉告诉我资源处理完之后清理正好。结果发现毫无效果。原因很简单process-resources阶段执行完后后续还有compile、process-classes等阶段。如果你的某个插件在compile或process-classes阶段又执行了一次资源复制那么你在process-resources阶段删掉的文件会重新被复制进target/classes。排查方法也简单执行mvn package -X开启调试输出看资源复制到底发生在哪个阶段再决定把清理动作放在prepare-package还是更靠后的阶段。我后来统一固定在prepare-package就是因为这个阶段处于资源复制完成、打包开始之前几乎不会被打断。4.2 第二次定位多模块项目里文件是被依赖模块带进来的有次故障出在聚合工程里。B 模块依赖 A 模块我在 B 模块里配置了删除 A 模块的某个配置文件认为这样 JAR 包里就不会有它。结果包构建完成后文件还是在。实际上Maven 依赖的传递机制根本不会把 A 模块 target/classes 下的文件直接拷到 B 模块里。B 模块依赖的是 A 模块打到本地仓库的 JAR 包里面有那个配置文件。你在 B 模块只删除自己的target根本没碰到依赖 jar 的内容。这种情况下要处理的是 A 模块的资源排除或者在 A 模块自己的打包配置中去掉这个文件。所以我后来给自己定了一条规则配置文件跟着它所属的模块走哪个模块的源文件就在哪个模块处理跨模块删文件基本是白费力气。4.3 第三次定位IDE 残留的 target 让 Maven 跳过资源复制还有一次更隐蔽。我同事用的 IDEA 经常自行编译target/classes里始终有一份旧文件。执行mvn package时Maven 检测到源码和资源文件没变化认为process-resources阶段不需要重新复制资源。我在prepare-package阶段配置的删除动作虽然执行了但下一个生命周期周期又构建一次IDE 或者其他插件又把旧文件带回target/classes最终打包时它又混了进去。这个坑的解决办法很直接在打包命令里带上 clean 就完事了。mvn clean package先从根上把target抹掉让资源复制必经全新状态执行后面你配置的删除动作就是真正的删除不会因为“文件没变化没复制”而被绕过。4.4 快速自查清单排查多了我总结出一份检查清单遇到“删不掉”的情况按顺序过一遍你的删除插件绑定在prepare-package或package之前的阶段了吗同一个构建周期里后续有没有其他插件把文件复制回来是不是多模块文件来自依赖 JAR而不是本模块 target执行命令是不是mvn package而不是mvn clean package如果是前者先加 clean 再试。有没有在src/main/resources之外通过excludes从源头排除最后解压 JAR/WAR确认文件是完整不存在而不是还在其他目录。这套检查清单帮我省了很多瞎折腾的时间也建议你收藏起来。5. 进阶玩法按环境、按阶段和按场景动态清理解决了基础删除问题后你会发现实际项目不可能只有“删”和“不删”两种状态。开发环境需要保留调试配置测试环境要保留测试数据生产环境要一切从简。这时候就得把清理策略做成动态的。5.1 用 Maven Profile 区分不同环境Maven Profile 就是为这个场景设计的。你可以为 dev、test、prod 分别定义不同的资源处理策略profiles profile iddev/id properties excluded.resource.pattern/excluded.resource.pattern /properties /profile profile idprod/id properties excluded.resource.patternapplication-dev.yml,application-test.yml/excluded.resource.pattern /properties /profile /profiles配合资源插件的动态参数打包时通过-Pprod激活生产配置让配置文件在编译打包阶段就被挡在门外。这样做的好处非常明显一套 pom 解决所有环境不会产生多个手工维护的 Maven 配置副本。例如你想让 antrun 插件只在 prod profile 下执行删除动作可以这样配置plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-antrun-plugin/artifactId version3.1.0/version executions execution idclean-prod-files/id phaseprepare-package/phase goals goalrun/goal /goals configuration target delete file${project.build.outputDirectory}/application-dev.yml/ delete file${project.build.outputDirectory}/application-test.yml/ /target /configuration /execution /executions /plugin在pom.xml中如果你希望这个执行只在 prod 下出现可以将整个execution放到profile的build节点下。这样 dev 环境打包时完全不执行删除逻辑所有配置都保留本地启动一点不受影响。5.2 与 Docker 镜像构建的衔接现在很多项目用 Maven 搭好 JAR 后马上交给 Docker 构建镜像。不少团队在pom.xml里用dockerfile-maven-plugin或直接mvn package后执行docker build。这时的文件清理要分两层看。第一层是 Maven 阶段用前面讲的方案把 JAR 里的内容清理干净第二层是 Docker 镜像阶段你构建 JAR 后希望清理target里的中间文件、或者把某些依赖包排除出镜像可以直接在 Dockerfile 里写入RUN rm -rf /app/target/*.tmp这种方式的优势是镜像构建过程更清晰哪些文件要进镜像、哪些不要运维同事对着 Dockerfile 就能看懂。我在实际项目中如果构建链路上已经存在 Dockerfile就尽量不再把复杂清理逻辑硬塞给 Maven避免两套构建工具互相打架。但如果你希望通过 Maven 直接管理镜像构建产出也可以在prepare-package阶段用 antrun 删除临时文件后再进入 Docker 插件流程。5.3 一个偷懒但好用的清理思路最后分享一个我一直在用的“偷懒”思路不要试图在打包阶段手工维护一份“删除清单”而是约定构建产物里只允许出现哪些东西。换句话说把excludes反转为includes也是一种策略。比如你的src/main/resources目录下文件很多真正需要进包的只有application.yml和logback-spring.xml那就可以写resource directorysrc/main/resources/directory filteringtrue/filtering includes includeapplication.yml/include includelogback-spring.xml/include includeMETA-INF/**/include /includes /resource这条配置走的是“白名单”逻辑不在名单里的文件天然不会进包你根本不用针对每个垃圾文件单独配置删除规则。用这种方式最大的价值是杜绝团队里“文件越放越多”的风气。因为新加一个资源文件如果没有主动加入 includes根本无法进入发布产物构建阶段就会暴露问题。比你在删除清单里不停加文件要省心得多。以我个人经验来说删除多余文件这件事百分之八十的情况都能用资源插件的 excludes 和 includes 解决剩下的再用 antrun 做定点清理。遇到“删不掉、又查不到原因”的诡异问题首先怀疑是不是多模块依赖再检查构建周期里的文件复制顺序大概率就能破案。最后再分享一个小技巧修改完 pom 之后养成先执行mvn clean package -DskipTests验证产物的习惯解压 JAR 前先用jar tf列出内容确认不需要的文件真的已经消失。这个动作只要十秒钟但能帮你省下未来线上事故排查的几个小时。