
我最早用 Spring Boot 还是 1.x 时代那时候最不理解的配置就是spring-boot-maven-plugin。明明项目能跑非要加个插件加了还时不时报错。后来真正被线上环境逼着把打包、部署、排查整条链路摸了一遍才发现这个插件的设计其实藏着不少学问。这篇文章就围绕标题里的两个关键词“打包”和“部署”把spring-boot-maven-plugin的核心配置、底层原理、常见坑位一次讲透。1. 重新认识这个插件它到底解决了什么问题很多初学者容易把 Maven 的maven-jar-plugin和spring-boot-maven-plugin搞混。前者只是把 class 文件和资源文件打成一个普通 jar后者做的是“可执行 jar”的组装工作。Spring Boot 项目如果只用普通 jar 打包运行时会直接报no main manifest attribute因为 Spring Boot 应用并不是简单运行一个main方法就能起来的它需要加载内嵌的 Tomcat、加载SpringApplication的启动逻辑、处理 classpath 下的所有依赖。1.1 为什么需要 repackage 机制插件最核心的 goal 是repackage它做的事情可以简单理解为把 Maven 默认打出来的普通 jar 当成“原材料”重新组装成一个包含BOOT-INF/lib目录结构的可执行 jar。这个结构里BOOT-INF/classes存放项目自身的 class 和资源文件BOOT-INF/lib存放项目所有依赖的第三方 jarorg/springframework/boot/loaderSpring Boot 自定义的类加载器为什么不能直接用 Java 默认的Class-Path机制因为java -jar在加载BOOT-INF/lib下面的嵌套 jar 时默认类加载器是不认识这种“jar 套 jar”结构的。Boot 自己写了一套LaunchedURLClassLoader专门处理嵌套 jar 的 URL 解析逻辑。所以你在MANIFEST.MF里会看到Main-Class指向的是org.springframework.boot.loader.JarLauncher而不是你自己写的那个Application类。1.2 可执行 jar 与普通 jar 的区别搞清楚这个区别调配置的时候就有方向了。可执行 jar 体积通常比普通 jar 大得多因为它把几百个依赖全部塞进了一个文件里。普通 jar 更像一个“半成品”需要配合mvn dependency:copy-dependencies把依赖拷到指定目录再用java -cp指定完整 classpath 才能运行。实际交付给运维或者部署到服务器几乎都是使用可执行 jar。但如果你开发的是一个公共 SDK 或者内部基础组件就不应该用spring-boot-maven-plugin的 repackage否则别人引入你的 jar 时会把 Spring Boot 的类加载器也带进去很容易出问题。这种场景应该单独配置classifier或者直接跳过 repackage。2. 核心配置逐项拆解每个参数背后都有个坑先给一个相对完整的插件配置示例下面逐个拆解关键项plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId version3.2.5/version configuration mainClasscom.example.AdminApplication/mainClass classifierexec/classifier excludes exclude groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /exclude /excludes includes include groupIdcom.example/groupId artifactIdinternal-sdk/artifactId /include /includes requiresUnpack dependency groupIdcom.example/groupId artifactIdnative-lib/artifactId /dependency /requiresUnpack /configuration executions execution goals goalrepackage/goal /goals /execution /executions /plugin2.1 mainClass启动类的精准定位大多数项目只有一个main方法正常情况下插件会自动扫描到不需要显式配置。但有几个场景必须手工指定项目里有多个main方法比如除了启动类还有单独的初始化命令行工具类模块化开发中启动类不在当前模块的源代码目录里使用自定义的 SpringApplication 子类或者字节码增强框架如 Javassist 代理时默认扫描可能失败不指定的情况下如果扫描到多个候选main类插件会直接构建失败。报错信息一般是Unable to find a single main class from the following candidates。2.2 classifier同时保留原始 jar 和可执行 jarclassifier这个参数我在很长时间里都没用到直到有一次要同时交付“可执行 jar”和“被其他模块依赖的普通 jar”才意识到它的价值。默认执行repackage时Maven 仓库里的那个 jar 文件会被原地替换成可执行 jar。如果你的项目被其他项目以依赖方引入对方拿到的是可执行 jar这个 jar 的目录结构是BOOT-INF/classes不是正常的 class 根路径直接导致编译期或运行期类找不到。配置classifierexec/classifier之后插件会把可执行 jar 命名为artifactId-version-exec.jar原始 jar 保留原文件名这样两个 jar 同时存在于 target 目录和 Maven 仓库互不干扰。发布到私有 Nexus 仓库时也能同时 install 两个 artifact。2.3 excludes 与 includes精准控制依赖范围有时项目引了一批全局 BOMBill of Materials依赖但实际启动时并不希望把它们打进可执行 jar。比如某些提供编译期注解处理的库运行时根本用不到打了反而增加误报风险。一个特别典型的例子是 Lombok。它在编译期做注解处理和代码生成运行时完全不需要。虽然 Spring Boot 的插件默认会自动排除一些已知的错误依赖但为了保险起见我还是习惯显式配置excludes exclude groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /exclude /excludesincludes的使用场景比较特殊默认所有依赖都会被包含配置includes之后反而会变成“只包含指定项”。它一般用在自定义依赖被插件错误过滤掉的场景比如某些私有 jar 的pom.xml声明不完整插件解析不到默认 scope可以用includes强制追加。2.4 requiresUnpack本地化原生库的最后一道关如果项目依赖里包含 JNIJava Native Interface类库或者需要在运行时解压出本机文件的组件requiresUnpack就会派上用场。正常情况下 Boot 的嵌套 jar 由自定义类加载器处理对外看起来是“另一个 jar”但 JNI 调用通常需要一个真实的文件路径来执行System.load。以 CK 或 Levenshtein 这类带.so文件的库为例不配置requiresUnpack运行时会报Could not load library或者java.lang.UnsatisfiedLinkError。配置之后插件启动时会把指定的 jar 解压到临时目录再加载原生库文件。注意requiresUnpack目前仅对 Spring Boot 2.x 和部分 3.x 版本生效4.x 对嵌套 jar 的加载机制做了部分调整建议先确认对应版本的官方文档。2.5 jvmArguments 与环境变量注入部署时最常见的一个需求就是给启动进程设置内存参数。你可以在启动命令里通过java -jar -Xms512m -Xmx1024m app.jar传参但如果你希望把 JVM 启动参数固化到 jar 内部的MANIFEST.MF里插件支持用jvmArguments配置configuration jvmArguments-Xms512m -Xmx1024m -Dfile.encodingUTF-8/jvmArguments /configuration不过我要泼个冷水这种方式在本地调试方便真正上生产环境JVM 参数最好由部署平台Kubernetes 的JAVA_OPTS环境变量、Dockerfile 的ENTRYPOINT统一管理。把参数写死在 jar 内部运维想临时调一个参数还得重新打镜像成本太高。3. 从打包到部署我用过的三种可靠方案配置讲完进入实战。部署方案本身五花八门但核心链路是一致的构建产物 → 运行环境 → 进程管理。下面按我实际用过的顺序讲三种方案从最基础的到容器化。3.1 传统方式直接使用 java -jar最简单的方式就是服务器上装 JDK把可执行 jar 传到服务器直接用命令启动java -jar admin-server-1.0.0-exec.jar \ --spring.profiles.activeprod \ --server.port8080实测中这个方案最大的痛点有两个一是进程守护单纯nohup不能满足服务异常退出后自动拉起的需求二是多实例部署时日志、端口、配置都需要额外脚本管理。我一般会配合systemd做一个服务单元来管理[Unit] DescriptionAdmin Server Afternetwork.target [Service] Userdeploy ExecStart/usr/bin/java -Xms512m -Xmx1024m -jar /opt/app/admin-server-1.0.0-exec.jar --spring.profiles.activeprod SuccessExitStatus143 Restartalways RestartSec10 [Install] WantedBymulti-user.target这里有个细节SuccessExitStatus143是为了处理 Java 进程被 SIGTERM 杀掉时退出码是 143 的情况如果不配置systemd 会认为服务异常退出触发误报重启。3.2 Docker 镜像方式分层结构是关键Spring Boot 3.x 起官方推荐通过layertools从 jar 中提取分层再配合 Dockerfile 利用镜像缓存加速构建。这个方案我非常推荐能明显减少镜像构建时间。先看常规打包方式FROM eclipse-temurin:21-jre COPY target/admin-server-1.0.0-exec.jar app.jar ENTRYPOINT [java, -jar, /app.jar]这种写法简单但缺点也明显只要代码有任何改动整个 jar 的层全部失效Docker 需要完整重新传输镜像层。对于频繁发布的应用来说这个传输成本很快就不能忍了。使用 layertools 的方式先在 pom 里配置好插件plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration layers enabledtrue/enabled /layers /configuration /plugin打包后 jar 内会多出一个layers.idx索引文件标记各文件所属的层。Dockerfile 写成这样FROM eclipse-temurin:21-jre AS builder WORKDIR /builder COPY target/admin-server-1.0.0-exec.jar app.jar RUN java -Djarmodelayertools -jar app.jar extract FROM eclipse-temurin:21-jre WORKDIR /app COPY --frombuilder /builder/dependencies/ ./ COPY --frombuilder /builder/spring-boot-loader/ ./ COPY --frombuilder /builder/snapshot-dependencies/ ./ COPY --frombuilder /builder/application/ ./ ENTRYPOINT [java, -jar, app.jar]官方默认将 jar 分成四层dependencies、spring-boot-loader、snapshot-dependencies、application。这样依赖层的镜像层基本不变只有application层会随代码频繁更新构建传输量非常小。有一点需要提醒spring-boot-maven-plugin的layertools提取方式在 Spring Boot 3.2 中有调整extract目录结构和 JarLauncher 的位置都有变化用了新版本如果启动报JarLauncher找不到先检查分层结构再用find命令确认app.jar的实际路径。3.3 原生镜像方式GraalVM 的冷启动优势Spring Boot 3.x 开始正式支持 GraalVM Native Image这能让应用直接编译成原生可执行文件启动耗时从秒级降到毫秒级内存占用也大幅下降。对于同机部署大量无状态服务或者 Serverless 场景这个优势非常明显。项目里要加一个专门的原生编译 profileprofiles profile idnative/id build plugins plugin groupIdorg.graalvm.buildtools/groupId artifactIdnative-maven-plugin/artifactId version0.10.2/version extensionstrue/extensions /plugin /plugins /build /profile /profiles构建命令是mvn -Pnative native:compile接口耗时、日志输出、反射顺畅度这些东西和 JVM 版本几乎一致但构建时间非常长内存要求也高实测 16GB 内存构建一个中小型项目都会吃紧。如果项目依赖里大量使用反射比如 MyBatis、Spring Data JPA 这些原生编译之前需要额外处理 reflect-config.json工作量和收益要合理评估。4. 生产环境构建的进阶玩法与经验坑打包部署这件事看起来不复杂真正在生产环境跑下来却会遇到各种千奇百怪的问题。下面分享几个我踩过且影响最大的坑。4.1 构建时跳过 repackage 的特殊场景有一次我搭了一个纯内部的数据迁移工具直接依赖项目里的多个模块的target/classes根本不需要把整个 Spring 应用打成可执行 jar。这时候如果还是默认执行 repackage构建时间会白白增加十几秒还可能因为找不到主类直接失败。处理方式很简单显式跳过mvn clean package -Dspring-boot.repackage.skiptrue或者把整个插件的 phase 从package改成一个永远不会执行的阶段plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId executions execution phasenone/phase /execution /executions /plugin第一个方法更常用临时跳过不需要改 pom第二个方法适合固化到某个 profile 里。4.2 resources 目录打包时最容易被忽略的问题Spring Boot 项目里src/main/resources下有application.yml、logback 配置、静态资源等。Maven 的maven-resources-plugin负责把这些文件复制到target/classes但默认会做编码转换和文件的再次过滤。如果你在配置文件里使用了project.version这类 Maven 占位符必须先在 pom 里开启资源过滤resources resource directorysrc/main/resources/directory filteringtrue/filtering /resource /resources这个机制在打包时非常有用可以根据 profile 动态替换配置文件里的版本号、环境标识。但如果过滤范围配置错误比如把二进制文件也加进过滤范围文件内容会被破坏导致图片打不开、keystore 证书校验失败。我建议只对application*.yml开启过滤其他资源保持原样复制。4.3 多环境配置profile 必须与打包方案解耦很多项目喜欢用maven-antrun-plugin或者profile来“控制打包哪一个环境的配置”。我强烈不建议这样做。Spring Boot 的--spring.profiles.active是一个运行时参数应该在部署阶段注入而不是打包阶段。把环境信息写死在 jar 里意味着同一份构建产物无法在 dev、staging、prod 三个环境中复用这跟持续交付的核心原则是冲突的。正确的做法是保持 jar 内一份统一配置通过启动命令或者环境变量注入环境差异java -jar app.jar --spring.profiles.activeprod --spring.cloud.config.urihttp://config-server:8888或者用环境变量方式SPRING_PROFILES_ACTIVEprod SPRING_DATASOURCE_URLjdbc:mysql://...这样一套产物走完全部环境出问题的时候只需要重新指定环境参数不需要重新构建重新测试节省的时间非常可观。4.4 spring-boot-maven-plugin 与 Docker 镜像的协同前面讲的 Docker 方案是针对单一 jar 的。如果项目是微服务架构有多个模块每个模块都要打镜像我就不会再手动写 Dockerfile 了直接用dockerfile-maven-plugin或者docker-maven-plugin来串联打包和镜像构建plugin groupIdio.fabric8/groupId artifactIddocker-maven-plugin/artifactId version0.43.2/version configuration images image nameregistry.example.com/admin-server:${project.version}/name build assembly descriptorRefartifact/descriptorRef /assembly dockerFileDockerfile/dockerFile /build /image /images /configuration /plugin不过我个人更推荐把镜像构建放到 CI/CD 平台GitLab CI、Jenkins、GitHub Actions里通过流水线触发。因为镜像构建涉及到仓库认证、缓存清理、安全扫描等能力这些本地 IDE 里执行都做不全面。Maven 侧只需要保证mvn clean package能产出分层 jar这是后续一切容器化流程的基础。4.5 JDK 版本兼容性插件 3.2 与 4.x 的坑Spring Boot 3.x 默认要求 JDK 17Spring Boot 4.0 已经要求 JDK 17 起步部分特性需要 JDK 21。插件版本和 JDK 版本绑定非常严格如果本地用 JDK 21插件用 2.x 版本大概率会遇到UnsupportedClassVersionError。一个真实的案例同事在本地用 JDK 21 把项目打成 jar上传到服务器之后启动报错提示UnsupportedClassVersionError: ... has been compiled by a more recent version of the Java Runtime。排查到最后发现服务器的 JDK 是 17但 Maven 侧没有配置java.versionSpring Boot 插件用了默认的 JDK 版本编译。解决办法是在 pom 的properties里显式声明properties java.version17/java.version maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target /properties这个配置看似基础但在多模块项目里经常被遗漏导致子模块编译版本不统一。尽量在父 pom 的properties中统一管理不要在子模块里单独覆盖。4.6 构建时报错的经典处理优先级我把这些年碰到过的构建报错按发生频率整理出一个处理顺序新项目遇到问题时可以直接按这个顺序排查报错形似优先检查项处理思路Unable to find a single main class启动类是否唯一在插件中显式配置mainClassFailed to execute goal repackage依赖是否完整确认mvn dependency:tree无runtime缺失项UnsupportedClassVersionErrorJDK 版本一致性检查java.version与服务器 JDK 是否匹配Jar is empty or does not exist子模块依赖顺序多模块构建加-pl、-am参数确保先 install 父和子模块Execution default of goal repackage failedjavac 版本与 Boot 版本是否兼容升级 Spring Boot 或回退 JDK 版本二选一BOOT-INF/lib is empty是否误用普通 jar 方式打包确认repackagephase 绑定到package这里面有一个非常容易忽略的点多模块项目父 pom 引了spring-boot-starter-parent子模块也引了spring-boot-maven-plugin但某个子模块没接父 pom 的pluginManagement导致插件版本漂移。我在实际项目里见过插件 2.7 和 3.2 同时出现在一个构建链里诡异问题层出不穷。统一版本只有一个办法所有模块的插件版本都走父 pom 的pluginManagement控制子模块只声明groupId和artifactId不写版本号。5. 常见问题与排查技巧实录结合使用群里和实际开发中的高频问题我整理一份速查版排查手册覆盖日常能碰到的绝大多数异常。5.1 jar 包能启动但访问 404 或路径不对这个问题的根源往往不是打包插件配置错误而是application.yml中配置了server.servlet.context-path导致所有接口路径多了一层前缀。打包本身没有任何问题。如果只改了这个前缀前端没有同步更新一旦新旧服务并行比如正在灰度切换就可能出现部分请求 404。排查时先不要急着怀疑打包先看server.servlet.context-path配置是否与访问路径一致RestController的类路径是否在启动类子包下ComponentScan默认扫描范围有限是否为静态资源JS、CSS设置了错误缓存策略5.2 Windows 打包 Linux 运行换行符与路径分隔符在 Windows 上打了 jar拿到 Linux 服务器上跑最典型的问题有两个一是 shell 脚本如果 jar 里内含换行符是\r\n执行时报bad interpreter二是配置里写死了\分隔符换到 Linux 直接运行时路径错误。解决方案代码里不要写死路径分隔符用File.separator或者 Path API如果必须包含 shell 脚本在pom.xml的maven-resources-plugin中设置${maven.build.timestamp}和lineSeparatorLF配置文件的编码统一为 UTF-8读文件时指定StandardCharsets.UTF_85.3 磁盘空间不足导致的构建失败可执行 jar 动辄上百 MB如果 target 目录或者本地 Maven 仓库所在磁盘满了插件就会在中途报错而且报错位置随机看起来毫无规律。比如解压依赖只做了一半后来永远卡在某个依赖上。遇到这种情况优先看df -h du -sh ~/.m2/repository把一些不再需要的旧版本依赖清理掉同时让 CI 流水线定期执行一次mvn dependency:purge-local-repository是不错的做法。磁盘空间管理这件事在本地开发不明显但放到流水线上频繁构建时仓库膨胀速度非常快。5.4 构建成功但运行报 ClassNotFoundException构建期一切正常运行期却报某个类找不到这大概率是依赖的scope配置错误。典型场景是把某个运行时需要用的工具库配成了provided它会出现在编译 classpath 中却不会打进可执行 jar。另一种情况在 Spring Boot 4.0 之后更常见新版本默认启用了 JVM 模块化行为检查和类路径空闲清理部分反射使用的类在启动时没有显式注册被判定为“不可访问”。这种问题的排查难度大建议在启动参数中加上-Dspring-boot.experimental.strong-encapsulation.bypasstrue或者显式添加--add-opens参数来逐项确认。5.5 版本升级带来的结构化调整Spring Boot 2.7 升级到 3.2 时spring.factories机制被AutoConfiguration.imports文件取代旧版很多自动配置直接失效。Maven 插件的打包逻辑本身没变但如果你之前使用了spring-boot-maven-plugin的jvmArguments或者自定义启动器升级后最好对比一下官方示例配置。4.x 里DataSourceAutoConfiguration被拆分spring-boot-starter-data-jpa不再默认引入 H2 驱动打包出来的 jar 可能比升级前小一大截。这种“变小的 jar”通常不是优化成功而是某些自动配置依赖的组件没打进来需要登录运行时环境先验证再放量。6. 把构建做成流水线CI 集成注意事项单机手动跑mvn clean package和真正上 CI 流水线看似同一个命令实际会暴露很多环境差异。我经历过 CI 打包成功、到生产却起不来的怪事逐层排查后发现是 CI 的 Maven 仓库和本地仓库不一致导致某依赖在本地有缓存、CI 上拿到了不同的快照版。6.1 流水线里必须固定的三个东西第一是 Maven 版本。.mvn/maven.config或mvnw能固定项目使用的 Maven 版本避免 CI 上装的是 3.9本地用的是 3.6行为出现差异。第二是 JDK 版本。本地编译用 JDK 17CI 可能默认 JDK 21输出 class 版本一旦升级到 65目标服务器跑不起来。最简单的办法CI 的 JDK 版本和飞测环境统一并在 pom 中显式声明maven.compiler.release。第三是依赖快照。生产构建非常忌讳使用SNAPSHOT依赖。如果项目里存在 SNAPSHOT 依赖每次构建都可能拉取到不同版本两个构建产物无法保证最终行为一致。建议给生产构建加一个 profile检查依赖版本里不出现SNAPSHOT或者在发布前统一执行mvn versions:use-releases来固化版本。6.2 构建产物命名与归档规范可执行 jar 的命名建议遵循artifactId-version[-classifier].jar格式git 提交号可以作为 classifier 的一部分体现这样在服务器上排查问题时一眼能看出跑的是哪个代码版本。我见过只按日期命名文件的两个构建产物放在同一个目录里光看文件名根本判断不了内容差异。同时建议把构建产物同时归档到私有仓库Nexus和 CI 的 Artifact 管理里。仅归档到 Nexus 时如果本地构建拉不到某个依赖你还能顺着版本号排查只留在 CI 里服务器想回滚旧版本就得重新触发流水线对于快速回滚场景非常痛苦。6.3 健康检查与优雅下线的配合部署阶段最容易忽略的不是“启动”而是“退出”。Spring Boot 默认启动时占用 8080 端口关闭时如果进程被杀得不干净端口处于TIME_WAIT状态新的容器起不来。推荐开启 actuator 的 health 端点并且在部署脚本里做启动探针management: endpoints: web: exposure: include: health,info endpoint: health: probes: enabled: trueKubernetes 部署时使用livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 5 periodSeconds: 5加上探针之后发布时旧实例先摘流量新实例健康检查通过再挂流量这个流程能避免绝大多数“发布后部分用户请求失败”的问题。可惜很多团队只把重点放在“能启动”上忘了“能正确接收流量”和“能优雅退出”同样重要。7. 踩坑小记几个真实案例的复盘7.1 案例一多模块项目打包顺序错乱导致线上运行时缺类一个微服务下有common、dal、service、web四个模块web层继承service层但service从dal和common拿依赖。开发时在 IDE 里一切正常因为 IDE 自己处理了模块间依赖。到了 CI 流水线如果只执行了一遍mvn clean install子模块的构建顺序是由 Maven 的 reactor 排序决定的应该没问题。但当时我们把web模块单独拿到另外一个流水线里打包依赖却还走本地仓库的旧版本一上线就ClassNotFoundException。排查之后你会发现这不是spring-boot-maven-plugin能解决的而是多模块依赖管理的老问题。解决方式是任何模块的打包必须保证其依赖的模块最新版本已install到仓库。再稳妥一点直接给 CI 流水线加第一步mvn clean install -DskipTests -pl common,dal,service -am。7.2 案例二Win 上打的 jar 在 Linux 上少文件非常诡异的一件事。同一个 jar在 Windows 上解压能看到BOOT-INF/classes/application-prod.yml部署到 Linux 上却报No active profile configured进程倒是起来了但所有配置都没加载。排查到最后原因藏在.gitignore里。当时把application-prod.yml误加进了.gitignore所以 CI 拉代码时这个文件根本没进工作目录Windows 本机因为有旧文件残留所以构建成功Linux 上是全新 checkout文件自然缺失。这个坑提醒我不要盲目相信本地能构建成功就代表流水线也能成功真正交付判据应该是流水线产物的完整性。检查 jar 内容可以快速验证unzip -p app.jar BOOT-INF/classes/application-prod.yml | head -207.3 案例三spring-boot-maven-plugin 不生效打包后仍然是个普通 jar某个模块引了spring-boot-maven-plugin配置也写了mvn package也执行了但打出来的 jar 解压后没有BOOT-INF也没有JarLauncher。大多数原因只有一个插件声明没有放到buildplugins里而是不小心放到了pluginManagement。pluginManagement只负责管理插件的版本和公共配置不负责实际执行。很多新手项目把这个搞混导致插件完全不生效。另外父 pom 用spring-boot-starter-parent时子模块如果重写了buildplugins必须显式加上plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin因为 starter-parent 本身只是通过pluginManagement固化了版本并不会自动给每个子模块都绑上插件的执行目标。8. 我的日常构建工作流最后分享一套我目前用得比较顺手的本地 CI 工作流程不一定适合所有团队但可以给想调整构建流程的人一个参照。8.1 本地开发阶段用mvn clean install -DskipTests先把全部模块安装到本地仓库IDE 里跑通单元测试确认逻辑行为需要做本地启动验证时直接mvn spring-boot:run不依赖完整的 jar 打包这一步会快很多因为省掉了 repackage 和所有文件复制本地尽量不执行repackage因为生成的 jar 很大且对本机运行没有额外帮助8.2 提交测试环境提交代码后 CI 自动执行mvn clean package -DskipTestsfalse单元测试全部通过后再生成可执行 jar测试环境直接拉取 CI 的构建产物通过--spring.profiles.activetest启动为了快速反馈测试环境可以加spring-boot-devtools的远程重启支持但生产环境绝对不能开8.3 上生产前手工或自动化触发生产流水线使用固定的 Maven 版本与 JDK 版本构建前拉取最新依赖CI 上最好禁用 SNAPSHOT 仓库构建归档到 Nexus通过部署脚本或者 Kubernetes manifest 指定版本号发布发布后立即看 actuator 的健康检查连续 10 次健康检查通过才视为发布成功用这套流程跑了几个项目之后我的感受是打包部署方向的问题六成以上不是配置写错了而是对构建链路的理解不完整。真正把spring-boot-maven-plugin的 repackage 机制、分层结构、构建与运行时解耦这几个点想清楚日常遇到的绝大多数打包部署问题都能自己推导出排查方向。如果你现在正被某个构建报错卡住不妨先按这篇文章的排查顺序捋一遍。大概率在检查mainClass、classifier、requiresUnpack和repackage.skip这几个参数的时候问题就已经定位到了。