ARTICLE · 1154781
Spring Boot Maven 插件目标集合:spring-boot:run / repackage / build-image 等 goal 的语义与参数速查
为什么
mvn package打出来的 jar 能java -jar直接跑,而裸 Maven 打的 jar 不行?秘密全在这个插件里。
很多人用了很久 Spring Boot,却从未单独留意过 spring-boot-maven-plugin 的存在。直到某天 CI 报了一个"主清单属性缺失 / Main-Class not found / No main manifest attribute"的错误,才第一次意识到——原来那个能双击跑起来的 jar,不是 Maven 打包的默认产物,而是这个插件额外动过手脚的。它就是让"普通 jar 变 FAT jar(可执行 jar)"的幕后推手。
这篇是这个 Spring Boot 知识点汇总手册"命令集合篇"里专门讲 Spring Boot Maven 插件的一辑。H01 讲了生命周期和 Maven 命令,H03 讲的是插件的 goal(目标)——spring-boot:run、spring-boot:repackage、spring-boot:build-image 这些。这三者是 Spring Boot 工程真正天天接触的插件入口。定位同样是速查页,收藏它,看 goal 语义、查参数、避坑,一次到位。版本以 Spring Boot 3.x 为准。
禁止事项:以下每个 goal、每个参数都是真实存在的,不编造不存在的 goal(比如没有 spring-boot:deploy 这种目标)。goal 的名词和参数以 Maven 约定 + Spring Boot 官方 goal 为准。
一、这个插件到底是什么
spring-boot-maven-plugin 是 Spring Boot 官方提供的 Maven 插件,它的核心工作是"接管 Maven 打出来的普通 jar,把它重构成一个可以直接 java -jar 运行的可执行 FAT jar(内含应用代码、所有依赖、内嵌 Tomcat/Jetty)",并附带提供了本地运行、生成容器镜像等目标。
它的坐标:
<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>3.3.x</version></plugin>关键事实:在 Spring Boot 的 parent(spring-boot-starter-parent)里,这个插件已经被默认声明并配置好了,你新建的 Spring Initializr 项目通常不需要手动加 <plugin>,只需要在 <build><plugins> 里显式引一次(一般带 <configuration> 或自定义)。但只要用到它提供的 goal(尤其是 repackage),最好在项目里显式引用插件声明,并把插件放在 <plugins> 里,否则某些自定义会不生效。
这个插件到底提供哪些可用的 goal,直接 mvn org.springframework.boot:spring-boot-maven-plugin:help 或看 help:describe 可列全。本节末尾会放一张"goal → 功能 → 典型命令"总表,正文按常用度展开讲。
为什么要理解这个插件的 goal,而不是只知道命令
很多学习资料把 mvn spring-boot:run、mvn package 当命令背,但如果不理解"这是插件的 goal",碰到下面这些情况就会很无助:
报错信息里出现 RepackageMojo、RunMojo这类类名,你没法把它和"哪个 goal"对上;想给 repackage加一个classifier、给run指定 profile,却不知道参数应该挂在哪个 goal 下;想知道"为什么 package会自动多打一个可执行 jar",你不明白是哪个 goal 绑到了package阶段。
所以本篇的讲解口径是"以 goal 为单元":每个 goal 给出了它的功能、可配参数、注意事项、典型命令。这样你不仅能"背出命令",还能在报错/加需求时自己反推"这是哪个 goal 在干活,参数应该怎么挂"。这是一种比记命令更能复用、更能扛住线上排查的知识组织方式。
插件里的 goal 大致分三类,先有个全局地图
把这个插件的 goal 按用途粗分三堆,记忆负担就小很多:
开发/构建类: run(本地跑)、repackage(打可执行包)、build-image(出镜像)、build-info(写构建元数据)。辅助/诊断类: help(列帮助)、start/stop(集成测试时启停进程)。AOT/原生类: process-aot、process-test-aot,走 GraalVM 原生镜像是会用到。
这三类合起来就是这个插件的全部面孔。日常高频只用前两类里的前三个(run、repackage、build-image),其余遇到再查。有了这张全局地图,往下逐条看就不会显得散。
二、spring-boot:run —— 本地跑起来
这是开发期最高频的 goal。它等价于"把你写好的 Application 主类直接跑起来",不必先打包成 jar。
# 最基本的本地启动mvn spring-boot:run# 把端口和启动参数用 -Dspring-boot.run.arguments 传进去mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=8081"# 带 Maven profilemvn spring-boot:run -Dspring-boot.run.profiles=dev# 同时给程序传参数和指定 Spring profilemvn spring-boot:run -Dspring-boot.run.arguments="--server.port=8081 --spring.datasource.url=jdbc:mysql://127.0.0.1:3306/db"功能拆解
spring-boot:run默认继承测试/编译产物,直接找主类(@SpringBootApplication所在类)并启动内嵌容器。适合本地开发、联调、验证配置。整个过程是"前台"的,进程不退出,会占用当前终端, Ctrl+C停掉。
主要可选参数(都以 -D 前缀给):
-Dspring-boot.run.profiles | -Dspring.profiles.active) | -Dspring-boot.run.profiles=dev |
-Dspring-boot.run.arguments | --server.port=8081 --active=dev | |
-Dspring-boot.run.jvmArguments | -Xmx512m | |
-Dspring-boot.run.main-class | com.example.MyMain |
注意点
Spring profile 用 -Dspring-boot.run.profiles传,不要用-Dspring.profiles.active——后者在spring-boot:run场景下经常不生效,因为它对应的是运行期参数,而这个 goal 的转发渠道是run.arguments。想用环境变量或者想让 profile 起效,-Dspring-boot.run.profiles=dev是最直觉可靠的。想热更新,配合 devtools; spring-boot:run本身不改代码重跑,改代码要重启进程(除非配了 devtools 自动重启)。当项目里跑了多个 Spring Boot 应用(微服务本地开发),记得用 server.port区分,别互相占端口。
spring-boot:run 的参数其实是一组前缀一致的家族
run 的可配参数有一个共同的命名规律:几乎都以 -Dspring-boot.run.* 开头。摸清这个规律,即使没背全某个具体参数,也能猜着写。核心的几个:
spring-boot.run.profiles | dev | |
spring-boot.run.arguments | --server.port=8081 | |
spring-boot.run.jvmArguments | -Xmx512m -Dfile.encoding=UTF-8 | |
spring-boot.run.main-class | com.example.Main | |
spring-boot.run.useTestClasspath | true | |
spring-boot.run.workingDirectory | /app | |
spring-boot.run.optimizedLaunch | false |
组合示例:
mvn spring-boot:run \ -Dspring-boot.run.profiles=dev \ -Dspring-boot.run.arguments="--server.port=8081 --app.retry=3" \ -Dspring-boot.run.jvmArguments="-Xmx512m -Xms256m"理解这几个前缀族,run 绝大多数参数需求都能直接照抄拼出来。对照 Maven 类同参数(-DskipTests、-P 等)也更容易分清——run 家族的参数是挂在这个 goal 底下的,不是全局的 Maven 属性,别的 goal 不认这个命名。
三、spring-boot:repackage —— 让 jar 变成可执行 FAT jar
这是整个插件里最核心、也最容易被忽略的 goal,也是"为什么 package 出来能跑"的关键。
# 显式执行 repackagemvn spring-boot:repackage# 通常你不需要单独敲它,因为它在 package 阶段就默认绑定执行了mvn package功能拆解
repackage 的作用:拿到 maven-jar-plugin 打出的普通 jar(只有你自己的 class,没有依赖),然后重写它,生成两样东西:
可执行 FAT jar:把依赖 jar 全部解压/内嵌进去,并写入 Main-Class: org.springframework.boot.loader.launch.JarLauncher和应用的Start-Class(真正的 main 类)。这样java -jar就能启动内嵌 Tomcat。原始普通 jar:通常带 .original后缀,留在target/,供需要"纯净依赖"的场景(比如 spring-cloud 特例)用。
repackage 何时自动执行
Spring Boot 3 里,repackage 默认绑定在 package 阶段的 repackage 上,你只要 mvn package 就会自动触发它。所以通常情况下你根本不需要单独敲 mvn spring-boot:repackage——它已经在你执行 package 时静默完成了。这句话是理解"为什么 package 就能跑"的钥匙。
那什么时候要显式敲?——当你想脱离默认绑定,自己控制时机时(比如自定义插件配置之后手动跑一次验证),或排查为什么打出来的 jar 不能执行(先 mvn spring-boot:repackage 单独重跑一次,确认不是依赖/插件顺序问题)。
FAT jar 的内部结构:看懂它,你就懂"能不能跑"
为了让"repackage 到底干了什么"落到实处,值得看一眼前两者打包差异的根子。可执行(FAT)jar 之所以能 java -jar 直接跑,秘密在它的内部布局和清单文件。用 unzip 打开一个 spring-boot:repackage 后的 jar 会看到:
# 看可执行 jar 的内部结构(FAT jar 的特征目录是 BOOT-INF)unzip -l target/myapp-0.0.1-SNAPSHOT.jar | head -n 20# 看清单文件里写了什么(关键是 Main-Class 和 Start-Class)unzip -p target/myapp-0.0.1-SNAPSHOT.jar META-INF/MANIFEST.MF一个典型的可执行 jar 里会有:
META-INF/MANIFEST.MF:Main-Class: org.springframework.boot.loader.launch.JarLauncher,和Start-Class: com.example.demo.DemoApplication。BOOT-INF/classes/:你的编译产物和application.yml等资源。BOOT-INF/lib/:所有依赖 jar,被解包平铺进去。BOOT-INF/classpath.idx、BOOT-INF/layers.idx等:Spring Boot 3 额外写的类路径/分层索引。
Main-Class 指向 JarLauncher(它不是你的应用主类,而是 Spring Boot 提供的启动器),再由启动器读取 Start-Class(你的真正 main),再加载 BOOT-INF/lib 里的依赖,最终启动内嵌 Tomcat。这就是"一个自包含、能双击跑"的产物背后的机制。
反过来,**你随手判断一个 target/*.jar 是不是可执行 FAT jar,最稳的判据就是"有没有 BOOT-INF 目录"**。没有 BOOT-INF 的一般是普通 jar(或 repackage 没生效),自然 java -jar 会报主清单缺失。
常见踩坑
主清单属性缺失 / No main manifest attribute:说明 repackage 没执行或没成功。最常见原因: <plugin>声明放到了<plugins>之外(比如放到了<pluginManagement>)、或多模块里父模块没正确生效。java -jar报这个错,先确认 target 里是不是可执行 FAT jar(unzip -l看是否有BOOT-INF/)。可执行 jar 不能被其他模块引用:FAT jar 里内嵌的依赖结构( BOOT-INF/lib)不适合作为普通依赖被依赖。因此要么用.original(普通 jar),要么把可执行 jar 的classifier改成exec,让 Maven 默认 artifact 保持普通 jar,Spring Boot 官方建议repackage配classifier优化结构。repackage是 pom 级 goal,别把它和"打包整个多模块"的完整动作混淆:mvn package才是连编译带测试带重打一整条。
相关配置(pom 里 plugin 的典型配置)
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><!-- 通常 version 由 parent 管理,可不写或显式指定 --><configuration><mainClass>com.example.demo.DemoApplication</mainClass><!-- 把可执行 jar 用 classifier 区分,保留普通 jar 供依赖 --><classifier>exec</classifier></configuration><executions><!-- 显式绑定 repackage 到 package 阶段(Spring Boot 3 默认已有,显式写出来便于理解/自定义) --><execution><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build>配置后典型产物:
# 默认无 classifier:一个可执行 jar(覆盖普通 jar)# `java -jar app-0.0.1-SNAPSHOT.jar` 可跑mvn clean packagels target/*.jar# app-0.0.1-SNAPSHOT.jar <- FAT/可执行# app-0.0.1-SNAPSHOT.jar.original <- 普通 jar(未重打)# 配了 classifier=exec:# app-0.0.1-SNAPSHOT.jar(普通,可被依赖) + app-0.0.1-SNAPSHOT-exec.jar(可执行)关于可执行 jar 的"可用边界",值得单独说清楚
理解了 FAT jar 是怎么造的,还有一个日常会产生错觉的点要交代:**FAT jar 适合"交付、部署、Docker",但不适合"当成普通依赖被别人引用"**。原因在于它内部结构的特殊打包方式,别人把它加进自己的 classpath 时,BOOT-INF/lib 里的那些平铺依赖并不在主 jar 的类加载路径上,会带来"引用不到类"的怪问题。
所以当你的 Spring Boot 工程同时扮演"可执行应用"和"被其他模块依赖的公共库"两个角色时,最稳的做法是:让 repackage 的产物用 classifier(如 exec)区分开——默认 artifact 保留普通 jar 供人依赖,-exec.jar 或带 classifier 的版本供人 java -jar 运行。这种方式在微服务拆包、公司内部公共依赖共建时尤其重要,能避免"别人引了你的 jar 却跑不起来"的续集。
另一个要点是关于"分层(Layered)"。Spring Boot 2.3+ 支持分层 jar(layers.idx),把依赖、应用代码、资源分层存放。好处是:构建 Docker 镜像/推送时,build-image 内部可以复用不变层(依赖层基本不变就不用重传),大幅加速镜像构建与拉取;同时分层也能帮助做"增量部署缓存"。对要频繁出镜像、容器镜像偏大的工程,这是打包侧值得专门配置的进阶项,而不只是"能跑就行"。
这两点合起来提醒:可执行 jar 不是"越小越普通越好",而是要为"在哪跑、被谁用"来定结构和 classifier。想清楚了,你的 repackage 才算真正用到位。
顺带补一句关于 "jar" 与 "war" 的取舍(虽然 Spring Boot 3 默认 jar):如果历史包袱/老派部署环境要求打成 war 放进外部 Tomcat,repackage 同样支持 war 的重打,机制类似(<packaging>war</packaging> + 非内嵌部署)。绝大多数新项目无需,但真要踩到"必须用外部容器跑"的场景,记住"repackage 不是只能处理 jar"这一点,能让你少走弯路。
四、spring-boot:build-image —— 生成 OCI 容器镜像
Spring Boot 3 里用这个 goal 直接产出容器镜像(不用手写 Dockerfile + 手动 docker build)。
# 生成 OCI 镜像(沿用默认 builder:Paketo buildpack)mvn spring-boot:build-image# 指定镜像名mvn spring-boot:build-image \ -Dspring-boot.build-image.imageName=registry.example.com/myapp:1.0功能拆解
build-image 用 Cloud Native Buildpacks(默认 Paketo builder)把你的应用打包成一个可直接 docker run 的 OCI 容器镜像。它不依赖 Dockerfile,由 buildpack 自动探测 Spring Boot 结构、选 JDK、配置启动。
它背后做的事情,大致可以拆成几步(方便理解它"为什么能免 Dockerfile"):
拉取/准备 builder:默认用 paketobuildpacks/builder:base这类基础 builder,内含生命周期和运行环境。运行 buildpack 生命周期的几个 step(detect/target/analyze/build/export):buildpack 会先探测你用的是不是 Spring Boot 工程、探测需要哪种运行库与 JRE/BellSoft Liberica/Retina 等,然后按指标组装出文件系统层。 导出为镜像:最终生成一个包含启动器、JRE、应用层的可运行镜像,并打上你指定的 imageName。
正因为整个生命周期由 buildpack 接管,你通常不需要自己写 FROM openjdk + COPY + CMD java -jar 的 Dockerfile。对"团队想统一容器构建标准、又不想维护一堆 Dockerfile"的场景,这是 Spring Boot 官方主推的一条省心事路线。
前置条件与注意点
需要能拉取 buildpack 资源:首次构建要联网下载 builder 及依赖( paketobuildpacks/builder相关的镜像),离线环境会有阻力。依赖本机 Docker 或兼容的镜像仓库环境:build-image 需要能访问镜像构建环境(一般是本机 Docker daemon)。没有装 Docker 会报错。 产物先是镜像,不是 target/*.jar;构建完成后可用docker run启动。常用参数( -D前缀):spring-boot.build-image.imageName指定镜像库/标签;-Dspring-boot.build-image.pullPolicy控制拉取策略;-Dspring-boot.build-image.createCache等。不想用 build-image、想用传统的 Dockerfile + docker build,完全没问题——build-image 只是官方提供的一条"免写 Dockerfile"的新路,不是唯一选择。
何时用:团队走 GitOps / 用较新的 CI,希望"从源码到镜像一步到位",不维护 Dockerfile 时;或想统一用 buildpack 作为镜像构建标准时。
一个实用技巧:build-image 里如何控制镜像名和 tag
多数人用它时的第一个需求就是"别给我默认那个 docker.io/library/<工程名>:latest,我要换成私服地址和版本号"。这通过 build.imageName 完成:
# 显式指定镜像库路径 + tagmvn spring-boot:build-image \ -Dspring-boot.build-image.imageName=registry.example.com/acme/myapp:1.2.3# 或放到 pom 的插配置里,让该环境构建统一走这个镜像名<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><image><name>registry.example.com/acme/${project.artifactId}:${project.version}</name></image></configuration></plugin>配好后 mvn spring-boot:build-image,产物就会带你的私服前缀 + 项目版本。这样在 CI 里推送镜像、在 K8s 里引用镜像名时都是确定的名字,避免"镜像名不一致造成到处找不到"的线上坑。这也是 build-image 场景里最值得记住的配置之一。
五、其它次要 goal 与"看插件里都有啥"
除了上面三个,插件还提供一些次要 goal,知道存在即可,用到时候再查其参数:
spring-boot:help | ||
spring-boot:build-info | build-info.properties(含 group、artifact、version、时间戳) | |
spring-boot:process-test-aot | ||
spring-boot:process-aot | ||
spring-boot:startstop |
次要 goal 里最值得先学会的一个:build-info
在前面那堆次要的 goal 里,build-info 是非常实用却常被忽略的一个。它的作用是生成一个 META-INF/build-info.properties,里面写入 group、artifact、版本号、构建时间等元数据。搭配 Spring Boot Actuator 的 /actuator/info(或 configprops 等),就能让运维在线上直接看到"这个包是哪个构建出来的、什么时候打的、是哪个版本",排障时"这线上跑的是不是最新版"这种灵魂拷问就有答案了。
# 生成 build-info.properties(配合Actuator/info端点展示)mvn spring-boot:build-info默认它并不在 package 链路里自动绑定,想让它随构建定期生成,可以在插件的 executions 里把它也绑进 build 流程。绑定后,curl /actuator/info 或 mvn package 打出的 jar 里就会带着这份构建元数据。对需要追溯版本、审计发布的环境而言,这几乎是零成本的"包可溯源"。
<executions><execution><goals><goal>build-info</goal></goals></execution><execution><goals><goal>repackage</goal></goals></execution></executions>其它 goal 的用途速记
剩下的 goal 大多围绕"集成测试启停"和"AOT 原生"两个冷门方向,这里用一段话串起来,见到能认、用到再去查即可:
** start/stop**:在集成测试(surefire/failsafe)过程中非阻塞地启动/停止应用进程,供测试代码以真实 HTTP 的方式打它,属于"起真服务测接口"的利器,比手动起 jar 再测更规整。** process-aot**:对应用做 AOT 处理,输出适合 Spring AOT 的副产物,是 GraalVM 原生镜像编译链路上的关键一环。** process-test-aot**:对应测试侧的 AOT,用原生路线跑测试时用。** help**:看插件所有 goal 与参数,本质是"这个插件能不能查我想要的参数"的自检入口。
这几类 goal 平时低频,但记住它们的"存在与归类",遇到冷门需求(比如"我要在测试里起真实的 Tomcat 应用")时就不会两眼一抹黑,能顺口说出"是不是可以靠这个 goal 来做"。
列全部 goal 的官方姿势
# 列出该插件所有帮助信息 / 所有 goal 的参数mvn org.springframework.boot:spring-boot-maven-plugin:helpmvn org.springframework.boot:spring-boot-maven-plugin:help -Ddetail这是"我想确认插件里到底有哪些 goal、每个 goal 的参数名是什么"的权威答案来源。比网上转述的二手列表更可靠,也符合"别靠记忆硬造参数"的原则——凡是怀疑参数名,就用 help 核实一遍最稳。
六、插件是否被绑定执行的排查思路
当你怀疑"我的 repackage 到底跑没跑"时,按下面顺序定位:
先看代码包结构: ls target/*.jar之后unzip -l app.jar | grep BOOT-INF。有BOOT-INF/(FAT 结构)说明 repackage 干过活;只有com/目录没有BOOT-INF则没重打。看 pom 生效配置: mvn help:effective-pom(H01 讲过)里搜spring-boot-maven-plugin,确认它是否存在于<build><plugins>,以及 version、execution(repackage 是否绑定到 package 阶段)。若只在<pluginManagement>里出现、<plugins>里没有,则不生效。看构建日志: mvn clean package(带-X或正常日志)里能看到类似spring-boot-maven-plugin:3.x:repackage执行的行。没出现说明绑定有问题。单独重跑一次: mvn spring-boot:repackage验证能否独立完成重构——能,则之前的问题多为绑定/顺序;不能,则重点看依赖和 JDK 配置。
拿到一个"跑不起来的 jar",完整排查流程串一下
把整篇的 goal 知识整合成一条实战 S.O.P.,方便直接照着走。场景:mvn package 成功,但 java -jar target/xxx.jar 启动报错、No main manifest,或起来就崩。
# 第 1 步:确认它到底是不是 FAT jar(有没有 BOOT-INF)unzip -l target/myapp-0.0.1-SNAPSHOT.jar | grep -c 'BOOT-INF/'# 第 2 步:看清单里的 Main-Class / Start-Class 是否齐全unzip -p target/myapp-0.0.1-SNAPSHOT.jar META-INF/MANIFEST.MF# 第 3 步:确认插件是否绑定,看构建日志 / effective-pommvn help:effective-pom | grep -A 3 'spring-boot-maven-plugin'# 第 4 步:单独验证 repackage 本身能不能跑通mvn clean packagemvn spring-boot:repackagejava -jar target/myapp-0.0.1-SNAPSHOT.jar判断逻辑一句话串起来:
没有 BOOT-INF/→ repackage 没干成 → 去查插件绑定(effective-pom里是否在<plugins>)、是否把<plugin>误放进了<pluginManagement>。有 BOOT-INF/但java -jar仍报NoClassDefFound→ 多半是某依赖没进BOOT-INF/lib(比如 scope 配成了provided,或该依赖在运行时依赖树里缺失),对照mvn dependency:tree核。Main-Class在,但Start-Class错或缺失 → 检查 mainClass 配置指向的类是否真实存在、包名是否有误。
这四条命令 + 三则判据,基本把"Spring Boot 可执行 jar 跑不起来"这一类问题覆盖住了。它也是"H01 生命周期、H03 goal、H04 进程排障"串联起来的一个典型应用——先分清是构建产物问题,再往运行时走。
六、一张"goal → 功能 → 典型命令"表
把上面的内容压成一张速查表:
run | mvn spring-boot:run | ||
repackage | mvn package | ||
build-image | mvn spring-boot:build-image | ||
build-info | |||
process-aot | |||
help | mvn spring-boot:help | ||
startstop |
一张按"我现在想干什么"选 goal 的决策速记
很多时候不是 goal 不懂,是临到用时拿不准"这步该用哪个"。给你一张反向选择的清单,照着问题挑即可:
想本地起服务、联调、边改边看 → mvn spring-boot:run(profile 用run.profiles,应用参数用run.arguments)。要给测试/部署一个能 java -jar的自包含包 →mvn package(自动走repackage)。打出来的 jar 跑不起来 / 想验证 repackage 有没有生效 → unzip -l看BOOT-INF/+mvn spring-boot:repackage单独重跑 +help:effective-pom查绑定。要免 Dockerfile 出一个容器镜像 → mvn spring-boot:build-image(记得配imageName)。想在 Actuator 里报出"当前包的版本/构建时间" → mvn spring-boot:build-info(并把它绑进 executions 随构建生成)。不确定插件里有哪些 goal 或参数名 → mvn ...:help官方核实,别猜。
这张速记的核心逻辑非常一致:**先明确你要的"产物或结果",再逆推出:"要本地跑→run,要可执行包→repackage,要镜像→build-image,要元数据→build-info"**。goal 是"输出导向"的,理解成"每个 goal 对应一类产出"就最省脑。
八、一句话收藏夹(goal 语义速记)
mvn spring-boot:run= 本地跑,参数走-Dspring-boot.run.*(profile 用run.profiles)。mvn package= 编译 + 测试 +repackage(FAT jar)一气呵成。可执行 jar = Main-Class: JarLauncher+Start-Class: 你的主类+BOOT-INF/。jar 打错/少依赖了 = 看 BOOT-INF/在不在、effective-pom里插件在不在<plugins>。--classifier=exec= 保住普通 jar、另出可执行 jar,结构更工程化。build-image= 免 Dockerfile 的容器镜像,依赖网络与 Docker/buildpack。报"主清单属性缺失" = 先查 repackage 是否绑定成功。
三个 goal 的状态机:run 到 deploy 的一条主干
把 run、repackage、build-image 看成同一条部署链路上的三个节点,就很好记它们各自查什么、先后怎么过渡:
开发(run)→ 构建(repackage,出 FAT jar)→ 容器(build-image,出镜像)→ 环境(跑起来)在实践中,这条主干两端还有两个常见衔接:
run 与 repackage 的衔接:本地先用 run调通,确认逻辑没问题,再package产出repackage后的 FAT jar。跑的既是同一套代码,但一个"跑工程源码"、一个"跑自包含包"。repackage 与 build-image 的衔接: build-image实际上也是在已有 FAT jar 基础上,用 buildpack 把它佐上 JRE 和启动器络成一个可运行镜像。所以"能mvn package成功"往往是build-image成功的前提——jar 都包不对,镜像自然也起不来。
记住这条主线,遇到"我该用哪个 goal"时,就顺着问一句"我现在在链路哪一端":在开发端用 run,在交付端用 repackage,要上容器用 build-image。这样选 goal 就不再纠结,而是水到渠成。
顺带一提,这三个 goal 的命名也有规律可循,能帮你记忆:run(跑)、repackage(重打包)、build-image(造镜像),动词直白对应产物;而 start/stop、build-info、process-aot 则各自对应"启停进程 / 写元数据 / AOT 处理"这几种侧面能力。名字即语义,见到不认识的多半也能猜个大概——这是 Spring Boot 插件 goal 命名相对友好的一点,比死记更要实在。
九、工程提醒(收藏向)
新建 Spring Initializr 项目,插件一般已被 parent 兜底;真要自定义(换 mainClass、配 classifier)才需要显式写 <plugin>。repackage的默认绑定在package阶段,别以为"要手动跑"。FAT jar 不能当普通依赖被别人引用,用 classifier保住普通 jar 是一条成熟做法。build-image有网络和 Docker 依赖,离线/受限环境要评估。排查可执行性问题,从 BOOT-INF/+effective-pom+ 构建日志三处下手,最快。想知道插件当下版本、有哪些 goal, mvn ...:help是官方答案来源,别靠记忆硬造参数。
收个尾:一张 goal 诊断的最终检查清单
把前面的实战判断压成最后一页自查清单,遇到"构建产物不对"时逐条过一遍:
[ ] 我要的是本地跑还是自包含包?——决定用 run还是package/repackage。[ ] target/*.jar里有BOOT-INF/吗?——没有就是 repackage 没干成。[ ] Main-Class是JarLauncher、Start-Class是我自己的主类吗?——都不对就查 mainClass 配置。[ ] 插件在 <build><plugins>里(还是不在<pluginManagement>)吗?——effective-pom里核实。[ ] 想上容器的, imageName按私服路径配了吗?配好再build-image。[ ] 想线上溯源版本的, build-info绑进 executions 了吗?绑了才有/actuator/info数据。[ ] 参数总是拿不准 / 报错里出现 Mojo 类名——用 mvn ...:help回到官方参数表。
把这 7 项变成肌肉记忆,Spring Boot Maven 插件这颗"隐藏的螺丝"在你手里就不再是玄学,而是一套可复述、可执行的判断流程。
到这里也就完赛了:run、repackage、build-image 这三个词各自背后都站着一个更细的目标与一组参数;你不是"记住三个命令",而是"记住三类产出"——本地跑、可执行包、容器镜像。遇到 Spring Boot 工程,随手就能说出这层对应,再用 mvn ...:help 随时核参数,这套插件的检修闭环就握在手里了。
记住一条主线:**run 帮你本地跑,repackage 给你可执行包,build-image 送它上容器**。这三件事串起来,就是 Spring Boot 工程从"开发"到"部署"在 Maven 层的主干。
这篇 goal 集合和本手册其它辑如何衔接
这一篇讲的是"Spring Boot Maven 插件里每个 goal 的语义",但它和构建命令那部分是咬合的:
和 Maven 命令: run/repackage/build-image都是"阶段-目标"机制的具体实例——repackage绑在package阶段、build-image直接敲 goal 名也行。命令管"哪条命令、哪个阶段",这里管"这个 goal 到底做了什么、参数挂哪"。和 H02(Gradle 命令):Gradle 侧对应物是 bootRun/bootJar/bootBuildImage。两边 goal 与 task 的名称不同,但"本地跑、打可执行包、出镜像"的三段语义是一一对应的。和 H04(Linux 排障): run起出来的、repackage打出来的、build-image推出的,最终都是一个 Java 进程。到了服务器上,它就是 H04 里ps/top/ulimit要盯的那个对象。构建玩具转成运行态故障,排查起点就在那里。
把这层衔接看清楚,你会明白:命令集合不是一个一个死记的孤岛,而是"怎么把一段 Java 代码从你的机器推向运行环境"这条主线上、不同节点上站着的不同工人。goal 只是其中一岗,但把这一岗的活儿看明白,整条链路的通感就通了。