ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

深入理解Maven POM:五大核心标签实战解析

2026/9/7 16:28:21 拓冰建站 浏览量
深入理解Maven POM:五大核心标签实战解析 1. 从一个让人头大的 POM 文件说起如果你接触过 Maven 项目大概率见过这种配置pom.xml 里堆着一堆properties、dependencyManagement、dependencies、build、plugins标签刚上手时真的分不清谁是谁。更头疼的是很多开源项目的 POM 写得极其复杂光 plugins 就列了十几个每个插件还带一堆 configuration看着就劝退。今天这篇就把这些标签一次性讲透包括它们各自管什么、相互之间是什么关系、哪些坑是新手必踩的、实际项目里该怎么组合使用。不管你是刚学 Spring Boot 的小白还是已经在写多模块项目的进阶选手这篇都值得看完。先说结论这五个标签本质上解决了三个问题——变量怎么定义、依赖怎么管、构建怎么做。properties是变量定义区dependencyManagement和dependencies管依赖的声明与版本控制build和plugins则是构建生命周期的定制入口。下面逐个拆但先别急着背概念我先带你建立一张全局地图搞清楚它们在一个项目里各自的位置。2. 先理清这五个标签在 POM 里的角色定位2.1 一个典型 POM 的骨架长什么样先看一个最常见的单模块 Spring Boot 项目。你打开 pom.xml通常会看到这样的结构从上到下依次是项目坐标groupId、artifactId、version、parent 继承信息、properties、dependencyManagement、dependencies、build——如果你打开过一些开源框架的源码还会看到这六个部分前后顺序基本固定因为 Maven 对 POM 的解析有严格的模型约定。project groupIdcom.example/groupId artifactIddemo/artifactId version1.0.0/version parent.../parent properties java.version17/java.version /properties dependencyManagement dependencies.../dependencies /dependencyManagement dependencies dependency.../dependency /dependencies build plugins.../plugins /build /project看到没有dependencyManagement里面套的也是dependencies标签这就让很多人第一次看时直接懵了——怎么同一个标签名套了两层这就是典型的不认识 XML 树结构导致的理解混乱。实际上两者语义完全不同外层dependencies是当前项目真正引入的依赖内层dependencyManagement里的dependencies只是版本锁定清单不直接引入任何东西。2.2 一个生活化的类比它们就像做饭的流程我习惯把这五个标签类比成做一道菜properties是菜谱顶部的用料清单备注比如酱油用海天的盐用低钠的——先把用的品牌、型号定下来后面所有地方引用同一个名称就行。dependencyManagement是**供应商价格表**它告诉你哪些食材可以从哪里买、什么规格但你没下单之前这些东西不会出现在你的冰箱里。dependencies才是真正下单的购物车你在购物车里加了菜菜才会到你手里。build是厨房的烹饪流程设置比如用蒸的还是煮的、最后装盘要不要撒葱花。plugins是厨房里的各种工具——蒸锅、烤箱、料理机每个工具干一件事build负责告诉你在哪个环节用哪个工具。这个类比先记在心里后面每个标签的细节就是往这个框架里填内容。2.3 为什么把这些标签搞明白很重要直接说后果。如果你搞不清dependencyManagement和dependencies的区别结局通常是两种情况中的一种要么在父 POM 的dependencyManagement里写了依赖子模块却到处报 ClassNotFoundException因为根本没引入要么所有依赖都写 version几十个子模块里版本号散落一地升级一次恨不得全局搜索替换。如果你搞不清build和plugins的关系最常见的问题就是打包出来的 jar 不是可执行 jarSpring Boot 项目 java -jar 直接报no main manifest attribute。本质就是少了 spring-boot-maven-plugin 这个插件而它必须配置在 build/plugins 里而不是随便放在别的位置。还有一个非常实际的经验当你去搜索某个报错时比如 failed to load plugins 或 cannot read properties of undefined很多问题根本不是 Maven 配置写错了而是插件版本不兼容或者构建顺序不对。比如热词里那些 grok build、pnpm approve-builds 报错本质上都是构建工具链里插件管理出了问题——只是技术栈不同底层逻辑完全一致。把这些 Maven 标签理解透换到 npm、Gradle、pnpm 时你也能触类旁通。3. propertiesPOM 里的全局变量区到底能省多少事3.1 properties 不只是版本号仓库大部分人用properties最常见的场景就是定义版本号properties java.version17/java.version spring-boot.version2.7.18/spring-boot.version lombok.version1.18.30/lombok.version /properties然后在 dependencies 里通过${spring-boot.version}引用。这是最基础的用法但它的能力远不止于此。properties本质是 Maven 模型的属性占位符机制你在properties里定义任意键值对整个 POM 中任何地方都可以用${key}引用。比如properties maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding skipTeststrue/skipTests /propertiesmaven.compiler.source和maven.compiler.target这两个属性是 Maven 编译器插件读取的标准属性定义了 Java 编译版本。project.build.sourceEncoding是 Maven 构建的标准属性控制源码文件编码。这些属性只要定义在properties里相关的插件会自动读取不需要你再单独写插件配置。你甚至可以定义自己的业务属性比如properties custom.api.urlhttps://api.example.com/custom.api.url /properties然后通过 Maven 的 resource filtering 机制在application.properties里用${custom.api.url}引用打包时自动替换成真实地址。这个技巧在做多环境打包时非常有用比手改配置文件优雅太多。3.2 properties 的继承特性与优先级properties是支持继承的。子模块的 POM 如果没有显式定义某个属性会从父 POM 中读取。这意味着你可以把整个项目统一的版本号、编码、编译级别全放在父 POM 的 properties 里子模块直接继承不用重复写。而且子模块可以覆盖父 POM 的同名属性——这个特性偶尔会被误用比如子模块覆盖了java.version但编译插件读到的可能是父 POM 的值导致编译版本和预期不一致排查很久才发现。所以我的建议是父 POM 的 properties 里只放全局统一、子模块不应该改的配置比如编码、Java 版本、所有模块共用的依赖版本。真正需要个性化的配置放到子模块自己的 properties 里并且尽量用不同的属性名避免继承覆盖引发的隐性坑。3.3 properties 与热词搜索中那些properties的关系你可能注意到热搜词里有 cannot read properties getusermedia、cannot read properties of undefined。这是前端 JavaScript 的报错和 Maven 的properties毫无关系——一个是 XML 里的属性声明机制一个是 JS 运行时访问对象属性报错。这里提一下是为了防止你搜索时被带偏。热词搜索本质上是一种噪音过滤能力技术关键词一样但语言、框架、上下文完全不同必须结合报错前缀来判断属于哪一套体系。比如带 reading 和 undefined 的基本都是 JavaScript 运行时错误带 Maven 或 pom.xml 的才是构建配置问题。4. dependencyManagement版本锁定的中央空调4.1 dependencyManagement 和 dependencies 最核心的区别直接把这两个标签的区别列个表这是本篇最重要的一张表对比项dependencyManagementdependencies是否直接引入依赖否只声明版本和管理策略是声明后立即加入 ClassPath子模块是否继承是所有子模块自动继承版本管理否子模块需要自己声明才会引入是否允许不写 version可以由父 POM 统一管理可以但必须依赖父 POM 的 dependencyManagement 已锁定版本适合场景多模块统一版本实际引入依赖单独使用结果项目不会新增任何 jar项目会新增 jar但版本可能乱记住一句话dependencyManagement 管版本号dependencies 管依赖本身。举个例子父 POM 中dependencyManagement dependencies dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.15.2/version /dependency /dependencies /dependencyManagement父 POM 自己不会引入 jackson-databind。但子模块 POM 中写dependencies dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependency /dependencies注意子模块里这个依赖没有写 version却能正常解析到 2.15.2因为父 POM 的 dependencyManagement 已经锁定了版本。这就是整个多模块项目版本统一的核心机制。4.2 为什么不推荐在子模块里写 version很多从单体项目转多模块的开发者习惯在子模块的依赖里手写 version结果就是版本散落。假设你有 10 个子模块都引了 Jackson但有的写 2.14.0、有的写 2.15.2一旦升级 Jackson你得挨个改 10 个文件稍微漏一个就是线上版本冲突。使用 dependencyManagement 后升级只需改父 POM 一处所有子模块同步。而且子模块里不写 version代码审查时也能一眼看出这个版本是父 POM 统一管理的而不是某个子模块自己拍脑袋定的。4.3 import 作用域把 BOM 引入 dependencyManagementdependencyManagement里还支持一种特殊作用域——import。这个在 Spring Boot 项目中极其常见dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-dependencies/artifactId version2.7.18/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement这个配置的意思是把spring-boot-dependencies这个 BOMBill of Materials物料清单里定义的所有依赖版本全部导入到当前项目的依赖管理中。这样你就可以在 dependencies 里放心写 Spring Boot 相关依赖而不写 version版本由 BOM 统一控制。这比自己手动一个个列 Spring 依赖版本要靠谱得多因为 BOM 是官方整理好的完整版本矩阵保证兼容性。4.4 一个容易忽略的细节dependencyManagement 里的 exclusions很多人不知道dependencyManagement里的依赖声明不仅可以锁版本还可以声明统一的依赖排除。比如你项目里统一不使用某个传递依赖可以在 dependencyManagement 里预声明排除规则dependencyManagement dependencies dependency groupIdcommons-logging/groupId artifactIdcommons-logging/artifactId version1.2/version exclusions exclusion groupId*/groupId artifactId*/artifactId /exclusion /exclusions /dependency /dependencies /dependencyManagement子模块引入 commons-logging 时排除规则会自动生效。不过这个用法实际项目中比较少见因为管理面太大容易误伤了解即可不建议滥用。5. dependencies真正把依赖拉进项目的购物车5.1 坐标、作用域与传递依赖dependencies里的每个 dependency 必须有 groupId、artifactIdversion 可写可不写取决于父 POM 是否已经管控。此外还有一个关键属性是 scope它决定依赖在哪些阶段生效scope生效阶段典型场景compile默认编译、测试、运行、打包全流程大多数业务依赖provided编译、测试servlet-api、lombok容器或 JDK 在运行时已提供runtime运行、打包JDBC 驱动test测试编译、测试运行JUnit、Mockitosystem编译本机 jar 包一般不推荐这个 scope 极其重要。我见过很多人把 lombok 配成 compile默认或者把 mysql-connector-java 配成 provided导致打包后运行报 ClassNotFoundException。实际经验是lombok 在 Maven 中应该配 provided因为编译后它不需要跟着走JDBC 驱动一般用 runtime因为编译时不需要直接引用它的 API。5.2 依赖冲突Maven 的就近原则和排除手段当多个依赖传递了同一个库的不同版本时Maven 遵循最短路径优先原则——离当前项目路径最短的那个版本生效。如果路径相等先声明者优先。这个规则平时不会觉得有啥但一出问题就让你怀疑人生。比如 A 依赖了 Guava 31.1B 传递依赖了 Guava 30.0而项目里直接用到了 Guava 31.1 才有的 API如果 B 的传递链更短导致 30.0 先生效编译期你能正常过运行期直接 NoSuchMethodError。排查依赖冲突的手段有两个执行mvn dependency:tree查看完整依赖树分析版本冲突路径。使用exclusions排除不需要的传递依赖dependency groupIdcom.example/groupId artifactIdsome-lib/artifactId version1.0.0/version exclusions exclusion groupIdcom.google.guava/groupId artifactIdguava/artifactId /exclusion /exclusions /dependency这是我在实际项目中排查编译正常、运行报错类问题最常用的两板斧。5.3 单模块项目dependencies 的省心写法如果是单模块项目且没有父 POM 管控那么你可以在 dependencies 里直接写全 version这没问题——反正整个项目只有一个模块没有统一版本的需求。但如果你未来打算拆分成多模块建议提前引入 dependencyManagement 结构哪怕暂时只有一个模块也能让你后续拆分时少改很多配置。6. build 与 plugins定制你的 Maven 构建流水线6.1 build 标签到底管什么build是 Maven 构建配置的根容器它负责回答三个问题构建产物叫什么、资源文件从哪来、构建时执行哪些插件。常见的子标签有以下几种build finalNamemy-app/finalName sourceDirectorysrc/main/java/sourceDirectory resources resource directorysrc/main/resources/directory filteringtrue/filtering /resource /resources plugins.../plugins /buildfinalName指定构建产物的文件名比如打成 jar 后叫 my-app.jar 而不是 my-app-1.0.0.jar。sourceDirectory指定源码目录默认是 src/main/java一般不用改。resources指定资源目录默认是 src/main/resources。filtering设为 true 表示允许对资源文件进行占位符替换也就是前面说的 properties 里定义变量后在资源文件中用${...}引用打包时自动替换。plugins构建插件的集合。还有一个容易被忽略但很实用的配置testResources指定测试资源目录默认是 src/test/resources。如果你有测试专用的配置文件不希望打进生产包可以在这里单独控制。6.2 plugins 是 build 的执行单元没有插件的 Maven 其实什么也做不了。Maven 本身只提供一个生命周期框架compile、test、package、install 这些阶段的具体行为全部由插件完成。所以plugins里配的就是在某个生命周期阶段执行什么任务。最常用的三个插件第一个是 maven-compiler-plugin。它负责 Java 编译。设置 Java 版本最直接的方式就是前面提到的 properties 中的maven.compiler.source和maven.compiler.target。但有些项目会发现设置了这两个属性后编译还是用的默认 JDK 版本这是因为 IDEA 的编译设置或 Maven 默认编译器版本覆盖了它。更稳妥的做法是在插件里显式配置plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.11.0/version configuration source17/source target17/target encodingUTF-8/encoding /configuration /plugin第二个是 spring-boot-maven-plugin。如果你用 Spring Boot这个插件几乎是必须的。它的核心功能是 repackage把项目打成一个可执行 fat jar里面包含所有依赖和启动脚本。不在 build/plugins 里配这个插件mvn package打出来的 jar 只是一个普通的 jar无法通过java -jar启动。热词里那个 no main manifest attribute 报错几乎都是这个原因。plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId version2.7.18/version executions execution goals goalrepackage/goal /goals /execution /executions /plugin注意 executions 里的 goal 配置。repackage这个 goal 默认绑定在 package 阶段如果你不写 executions插件确实也会执行默认行为。但显式写出来更清晰而且有些版本如果不显式配置执行阶段打包后 jar 还是不能运行我踩过这个坑。第三个是 maven-surefire-plugin。它负责运行单元测试。默认会用 JUnit 4 或 JUnit 5 自动匹配测试类。你可以在配置里指定跳过测试plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-surefire-plugin/artifactId version3.1.2/version configuration skipTeststrue/skipTests /configuration /plugin但这只是跳过执行测试代码依然会编译。如果你想连测试代码一起跳过编译用maven.test.skiptrue。两者的区别很微妙但对构建速度影响很大CI 环境里如果不需要跑测试用后者的构建时间能省不少。6.3 pluginManagement 和 plugins 的区别这就跟 dependencyManagement 和 dependencies 的关系一模一样。pluginManagement是在父 POM 中统一管理插件版本和配置子模块声明使用插件时可以不写版本。plugins才是真正在构建中启用插件的地方。举个例子父 POMbuild pluginManagement plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.11.0/version /plugin /plugins /pluginManagement /build子模块要使用这个插件只需build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId /plugin /plugins /build插件版本自动从父 POM 的 pluginManagement 中解析。我强烈建议多模块项目把插件版本集中在 pluginManagement 中管理跟依赖版本集中在 dependencyManagement 中是同一个道理——少改配置、避免各子模块插件版本不一致导致的诡异构建问题。6.4 一个实际的 build 配置示例这里给一个我项目里常用的多模块父 POM 的 build 段精简版build finalName${project.artifactId}/finalName pluginManagement plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId version2.7.18/version /plugin plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.11.0/version configuration source17/source target17/target encodingUTF-8/encoding /configuration /plugin /plugins /pluginManagement /build子模块中如果某个模块是可启动的应用就在自己的 build/plugins 里加 spring-boot-maven-plugin 并执行 repackage普通的 jar 模块不需要加打包成普通 jar 即可。这样既保证了模块职责清晰又避免了所有模块都打出 fat jar 的低效问题。7. 五者联动一个多模块项目的完整 POM 解读7.1 父 POM 的完整配置把前面讲的五块拼起来看一个典型的多模块项目父 POM。这才是全文最关键的一节建议你拿出自己的项目对照着看。project modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdmy-project-parent/artifactId version1.0.0/version packagingpom/packaging modules modulecommon/module moduleservice/module moduleweb/module /modules properties java.version17/java.version spring-boot.version2.7.18/spring-boot.version mybatis-plus.version3.5.3.1/mybatis-plus.version lombok.version1.18.30/lombok.version /properties dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-dependencies/artifactId version${spring-boot.version}/version typepom/type scopeimport/scope /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version${mybatis-plus.version}/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version${lombok.version}/version /dependency /dependencies /dependencyManagement build pluginManagement plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId version${spring-boot.version}/version /plugin plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.11.0/version configuration source${java.version}/source target${java.version}/target encodingUTF-8/encoding /configuration /plugin /plugins /pluginManagement /build /project注意这个文件里 module 的列表是 common、service、web但没有在父 POM 的 dependencies 里引入任何依赖所以父 POM 本身是不携带任何 jar 的它只负责版本管控和构建配置下发。这个只管控、不引入的原则是父 POM 设计的核心也是区分你懂不懂多模块依赖管理的关键。7.2 子模块 POM 的两种典型写法普通依赖模块commonproject parent groupIdcom.example/groupId artifactIdmy-project-parent/artifactId version1.0.0/version /parent artifactIdcommon/artifactId dependencies dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId scopeprovided/scope /dependency /dependencies /project这个模块里两个依赖都没写 version版本从父 POM 的 dependencyManagement 里继承。lombok 配了 provided因为它只在编译期需要。可启动应用模块webproject parent groupIdcom.example/groupId artifactIdmy-project-parent/artifactId version1.0.0/version /parent artifactIdweb/artifactId dependencies dependency groupIdcom.example/groupId artifactIdcommon/artifactId version1.0.0/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build /projectweb 模块依赖 common 模块版本写 1.0.0——这时 version 必须写因为这是模块间依赖不属于 dependencyManagement 管理的第三方依赖版本。构建时通过 spring-boot-maven-plugin 打出可执行 jar。7.3 从这套组合中学到的核心经验把上面的父 POM 和两个子模块 POM 通读一遍你会发现一个规律父 POM 只做三件事——定义变量、统一版本、统管构建插件子模块只做两件事——声明自己需要的依赖、声明自己需要的插件。各司其职互不越界。这就是 Maven 多模块项目最佳实践的核心思路。如果你打开一个结构理想的多模块开源项目会发现它的 POM 分层就是这么清晰。反观很多能跑但很痛苦的项目问题几乎都出在越界——要么子模块里写满了 version要么父 POM 里引入了实际依赖要么 build 配置五花八门、每个模块各写一套编译器参数。8. 常见问题与排查技巧实录8.1 报错速查表平时在社区里看到的各种 Maven 相关问题大多都能归到这次讲的五类标签上。我整理了一个高频问题速查表报错或现象大概率原因解决方向子模块依赖找不到版本dependencyManagement 没配或没继承检查父 POM 的 dependencyManagement 和子模块的 parent 声明父 POM 里写了依赖但子模块用不了dependencies 和 dependencyManagement 混用把版本管理挪到 dependencyManagement子模块自己声明 dependencies打包后 java -jar 报 no main manifest attribute缺少 spring-boot-maven-plugin在可启动模块 build/plugins 加 spring-boot-maven-plugin 并配置 repackage编译版本不对用了 JDK 17 却编译成 8properties 或 compiler plugin 没配置对检查 maven.compiler.source/target 和 maven-compiler-plugin 的 configuration运行期 NoSuchMethodError依赖版本冲突用 mvn dependency:tree 排查用 exclusions 排除多余版本资源文件中变量没替换resources 的 filtering 没开build/resources/resource 设置 filteringtrue子模块插件版本不一致插件版本散落在各个子模块把插件版本集中到父 POM 的 pluginManagement8.2 实战中的三个血泪经验经验一永远不要在子模块的 dependencies 里写 version。这听起来有点绝对但在多模块项目里这是铁律。我有一次接手一个老项目20 多个子模块里手写了几十个版本号特别是 Jackson 和 Guava同一个库在三个模块里是三个版本。排查一个序列化问题花了整整两天最后发现是子模块版本不一致导致的。把版本全部收到父 POM 的 dependencyManagement 后整个世界清净了。经验二改动父 POM 后要 clean 再 install。很多人改了父 POM 的 dependencyManagement 或 pluginManagement然后在子模块里 mvn compile结果发现还是旧版本生效。这是因为父 POM 没有重新安装到本地仓库子模块用的还是旧的父 POM。改了父 POM 后先在父目录执行mvn clean install -N-N 表示只构建父 POM不构建子模块再构建子模块这个问题就消失了。经验三配置了 properties 但打包没生效先检查 filtering。我见过很多次这种操作在 properties 里定义了环境变量也在资源文件里写了${env.name}打包后却原样输出。原因就是 resources 的 filtering 没开。这个属性默认是 false不手动打开就不会做占位符替换。另外需要注意filtering 开启后会影响构建速度因为 Maven 会逐个文件扫描替换如果资源文件很大建议只对需要替换的文件单独开 filtering。8.3 与前后端构建工具横向对比既然热词里出现了 pnpm、Gradle 这些构建工具的报错顺便说一句构建工具的管理思路是相通的。pnpm 里的pnpm approve-builds对应的是信任某些依赖的安装后脚本本质上就是插件/脚本执行的安全策略Gradle 里的dependencies配置块和 Maven 的 dependencies 表达的是同一个概念只是写法不同npm 里的 devDependencies 对应 Maven 的 provided/test scope。理解了 Maven 这套声明-管理-执行的模型换工具只是换个语法的事。8.4 热词里那些报错为什么会和 Maven 扯上关系最后聊一个现象你搜索 failed to load plugins 时结果里既有 Maven 的插件加载报错也有 VS Code 扩展加载失败还有前端 webpack 的插件加载问题。关键词一样体系完全不同。技术排错的第一原则永远是先确认你处在哪个上下文——是 Java 构建、前端打包、还是 IDE 运行时。不要被搜索结果的前几条带走结合你的工具链和报错前后文判断。这也是为什么我前面反复强调标签要放到全局地图里理解同一个名词在不同的地图里含义可能完全不同你只有先建立起自己的项目地图才能快速定位到正确的问题域。回到本文开头的问题——properties、dependencyManagement、dependencies、build、plugins 的作用和区别。如果你能用自己的话解释清楚dependencyManagement 管版本dependencies 管引入properties 管变量build 管流程plugins 管工具并且能在父 POM 和子模块之间正确分配这些标签的职责那么这篇就没有白看。剩下的就是在实际项目里多写、多试、多踩坑配置这种东西光看不练是记不住的。