Java基础、中级、高级、架构面试资料

Spring Cloud 官宣分手 starter-parent,详解 BAT 都在用的“祖-父-子” 3 代模式

JAVA herman 11浏览
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog2,发送下载链接帮助你免费下载!
本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog2,之前的微信号好友位已满,备注:返现
受密码保护的文章请关注“业余草”公众号,回复关键字“0”获得密码
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领
腾讯云】1核2G5M轻量应用服务器50元首年,高性价比,助您轻松上云

最近同事问我了一个好问题,大致的意思是,他搞不明白 Spring Boot、Spring Cloud 体系中 pom.xml 的各种配置。

说白了,就是 Parent、dependencyManagement、dependency、pluginManagement、plugins 等这些配置项,各有什么作用,以及是怎么配合的。为什么要搞这些层层套娃的结构?

哎,要解释清楚这个问题呀,得从软件工程上的角度,架构上的角度来说。那接下来,我们就一起搞懂这些眼花缭乱的配置标签,来一次彻底的梳理。

Maven 配置的五虎将

这五虎上将的配置,其实就是各司其职的架构设计而已。先来看看 parent。

parent 的作用

parent 是 Maven 项目继承机制的核心。我们常见于下面这样的定义。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.0.1</version>
</parent>

这个定义,让我们实际上继承了“上百个”精心配置的默认设置。

  • 依赖版本管理:Spring Boot 所有官方组件的版本号
  • 插件默认配置:编译插件、打包插件的优化参数
  • 资源过滤规则:application.yml 中的变量替换
  • Profile 预设:开发、测试环境的默认配置
  • 编码与格式化:UTF-8 编码、Java 版本等基础设置

简单来说,它的作用就是,继承规则。统一版本、构建规范、依赖管理、约定插件,减少重复配置,让子项目开箱即用

dependencyManagement 的作用

接下来,我们来看看 dependencyManagement,这是最容易被误解的元素。它其实是只声明版本,不引入依赖

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2025.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

这段配置是告诉 Maven,更是约束其它使用者。如果你想用 Spring Cloud 组件,我提供了版本清单,但你必须用 dependency 显式声明需要的模块。

需要注意的是,在 dependencyManagement 中的依赖,子项目不会自动获得。这也是为什么一些同事有疑惑的地方,明明 dependencyManagement 有了配置,为什么还需要 dependency 一遍?

这就是 dependencyManagement 的作用,它是一个弱规范,弱约束,约束版本,约束依赖用的。

有了它,子项目只需写 groupIdartifactId无需指定 version。尤其是公司项目很多的时候,这种规范的好处越能得到体现。

dependencies 的作用

dependencies 才是真正的依赖加载器。

这才是实际引入 Jar 包的地方。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!-- 无需版本号,从 parent 继承 -->
    </dependency>
</dependencies>

说白了,只有写在这里的依赖,才会出现在你的 classpath 中,参与编译和运行。

当然,这里也可以跳出 3 界之外,自行引入其它依赖,指定其它版本。

但一般不建议这样做,这样会破坏整个集体公司的依赖规范体系。有些管理严的公司,甚至限制只有指定的人员能改子项目或父项目的 pom 依赖,避免出现“野依赖”。子项目的 pom 只有研发人员在调试时进行变更,调试通过后申请有权限的人员变更,并由他们 push。

pluginManagement 的作用

dependencyManagement 理念相同,但作用于构建插件

<pluginManagement>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>3.2.0</version>
            <configuration>
                <layers>
                    <enabled>true</enabled>
                </layers>
            </configuration>
        </plugin>
    </plugins>
</pluginManagement>

它的作用是,统一插件版本和配置,但不会自动执行,需要在子项目的plugins中显式引用。

plugins 的作用

如果有 pluginManagement,那么再引入 plugin 的时候,也可以无需自定版本。

<plugins>
    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <!-- 继承 pluginManagement 的配置 -->
    </plugin>
</plugins>

写在plugins中的插件会绑定到 Maven 生命周期(compile/package 等),真正参与构建过程。

完整的 dependency 流向图

pom.xml 里的各个标签,相互协作,默契配合,共同串联起整个 jar 依赖与构建打包部署等。

grandparent-pom.xml (spring-boot-starter-parent)
    ├─ dependencyManagement: [spring-web:6.1, spring-data:3.2, ...]
    ├─ pluginManagement: [spring-boot-maven-plugin:3.2.0, ...]
    └─ properties: <java.version>17</java.version>

parent-pom.xml (company-microservice-parent)
    ├─ parent: spring-boot-starter-parent
    ├─ dependencyManagement: 
    │   └─ import spring-cloud-dependencies (2023.0.0)
    └─ pluginManagement: 
        └─ 配置 dockerfile-maven-plugin

child-pom.xml (order-service)
    ├─ parent: company-microservice-parent
    ├─ dependencies: 
    │   ├─ spring-boot-starter-web (自动获得版本 6.1)
    │   └─ spring-cloud-starter-gateway (自动获得版本 4.1)
    └─ plugins: 
        └─ spring-boot-maven-plugin (自动获得 docker 配置)

整个流程解析简化如下。

  1. order-service 声明 parent,继承所有版本清单
  2. 引入 spring-boot-starter-web 时,Maven 从 dependencyManagement 查到版本
  3. 打包时,spring-boot-maven-plugin 的配置从 pluginManagement 继承

最佳实践

不少企业内部,尤其是小企业,可能没有这些三段式的架构规约。

我本人摸滚打爬了多年,经过多年实践,推荐大家采用祖-父-子三代 POM 管理或约束模式。

第一代,约定官方 Parent

公司级根 POM,或者说是祖 POM。

<!-- 公司级根 POM -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.0</version>
</parent>

第二代,公司级 BOM

公司级 BOM,也被内部称为父 POM。

<!-- company-bom/pom.xml -->
<artifactId>company-dependencies</artifactId>
<packaging>pom</packaging>

<dependencyManagement>
    <dependencies>
        <!-- Spring Cloud -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2023.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <!-- 自研组件 -->
        <dependency>
            <groupId>com.company</groupId>
            <artifactId>common-security</artifactId>
            <version>1.2.0</version>
        </dependency>
    </dependencies>
</dependencyManagement>

<pluginManagement>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <image>
                    <builder>paketobuildpacks/builder:base</builder>
                </image>
            </configuration>
        </plugin>
    </plugins>
</pluginManagement>

第三代,业务项目

业务项目,就是最后的,受前两代弱约束。

<!-- order-service/pom.xml -->
<parent>
    <groupId>com.company</groupId>
    <artifactId>company-dependencies</artifactId>
    <version>1.0.0</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.company</groupId>
        <artifactId>common-security</artifactId>
    </dependency>
</dependencies>

<plugins>
    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
</plugins>

这样做的收益简单总结如下。

  • 版本集中管控,一处修改,全公司生效
  • 业务项目极度简洁,专注业务依赖
  • 构建配置标准化,避免每个团队重复踩坑

我不知道有多少网友是这样做的,或许也有不少人理解不了这样做的好处,也没关系。随着工作年限的增加,从工程化、架构设计等角度来思考,会有明白的那一天的。

Spring Cloud 为什么抛弃 starter-parent?

原本上面的一切都很好,但是 Spring Cloud 出现了“叛逆”,它抛弃了 starter-parent。

正如我的这篇文章《https://mp.weixin.qq.com/s/ZzfF025vRQkilz94W0T5tQ》中所说,Spring Cloud 移除了spring-cloud-starter-parent,并不在推荐使用它,转而拥抱了spring-cloud-dependencies

注意,下面这个用法已经被抛弃了。

<!-- 旧的用法 -->
<parent>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-parent</artifactId>
    <version>2024.0.0</version>
</parent>

究其原因,就是spring-cloud-starter-parent有它的局限性。

  1. Parent 只能有一个:一旦用了 Cloud 的 parent,就无法再用公司自定义的 parent
  2. Cloud 与 Boot 版本强绑定:Cloud parent 内部指定了 Boot 版本,升级不灵活
  3. 生态割裂:Netflix、Alibaba 等组件需要额外的 dependencyManagement 导入,配置分散

新范式纯 BOM 化设计

现在 Spring Cloud 官方推荐下面这样的使用方式。

<!-- 现代配置(推荐) -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.5.0</version>
</parent>

<dependencyManagement>
    <dependencies>
        <!-- Spring Cloud 作为 BOM 导入 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2024.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <!-- Spring Cloud Alibaba -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2024.0.0.0-RC1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

为什么要拥抱spring-cloud-dependencies?这就是进步?

哎,你别说。这种做,有以下 4 种好处。

  1. 单一职责:Boot 管框架,Cloud 管分布式,职责清晰
  2. 组合灵活:可同时导入多个 Cloud 生态的 BOM(Alibaba、Azure、AWS)
  3. 升级友好:Boot 和 Cloud 版本可独立迭代,互不干扰
  4. 符合 Maven 哲学:parent 用于继承“行为”,BOM 用于管理“版本”

官方原话是这样说的。

spring-cloud-starter-parent was designed for a simpler era. Modern microservices require composable dependency management, not hierarchical inheritance.

翻译过来就是,spring-cloud-starter-parent是为更简单的时代而设计的。现代微服务需要的是可组合的依赖管理方式,而非层级继承。说白了,这里更能体现组合优于继承

注意,上面有一个<scope>import</scope>。它是 Maven 2.0.9+ 的特性,它允许“导入”另一个 POM 的dependencyManagement段,效果相当于复制粘贴,合并多个dependencyManagement。这是实现 BOM 组合的关键。

版本号的覆盖优先级应该都懂吧。子项目显式指定的 version > 第二代 POM 的 version > 第一代 parent 的 version

注意,千万不要在 dependencyManagement 中写 <scope>compile</scope>。因为这会强制所有子项目都必须引入该依赖,失去了“按需声明”的灵活性,违背了 BOM 的设计初衷。

总结

这一切都是架构演进背后的架构思维,祖-父-子三代 POM 管理模式,是老外总结的最有解,推荐大家使用。

另外,从spring-cloud-starter-parent的退场,我们看到了从继承到组合的架构范式转变。

模式优点缺点适用场景
Parent 继承简单直接僵化、不可组合单体应用、小型项目
BOM 组合灵活、可扩展配置稍复杂微服务、多生态集成

记住,记住一句话,好的依赖管理,不是让配置变少,而是让变化可控

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加作者微信号:xttblog2。备注:“1”,添加博主微信拉你进微信群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作也可添加作者微信进行联系!

本文原文出处:业余草: » Spring Cloud 官宣分手 starter-parent,详解 BAT 都在用的“祖-父-子” 3 代模式