企业 Maven 依赖管理层次结构设计

企业 Maven 依赖管理层次结构设计

准备工作

既然是企业使用,那么一定要有企业的 Nexus 私服,通过私服可以加快公司内部常用第三方依赖的下载速度,最重要的还是可以将企业内部的项目 deploy 到私服供企业内部项目使用。

关于搭建 Nexus 以及简单的配置等内容可以参考:

企业 Maven 依赖管理层次结构设计 - 附录》中的 1 和 3 节内容。

本文是直接使用 Nexus,所以默认认为已经存在合理的 Nexus 配置和可用的帐号密码:

企业 Maven Mirror 地址:

http://localhost:8081/repository/maven-public/

帐号密码:admin/123456

特别说明:maven-public 是一个 Nexus 组,其中可以配置包含哪些 Repository,默认包含 Maven Central 的代理,企业内部的 Releases 和 Snapshots。为了加速还可以配置上 AliRepo(可以参考《企业 Maven 依赖管理层次结构设计 - 附录》中第 2 节的内容)。

想要 deploy 到 Nexus 私服,还需要每个用户(开发人员)在自己电脑上配置 Maven 的 settings.xml,关键的部分配置如下:

\<servers\>
    \<!-- 快照版用户配置 --\>
    \<server\>
        \<id\>nexus-snapshots\</id\>
        \<username\>admin\</username\>
        \<password\>123456\</password\>
    \</server\>
    \<!-- 发布版用户配置 --\>
    \<server\>
        \<id\>nexus-releases\</id\>
        \<username\>admin\</username\>
        \<password\>123456\</password\>
    \</server\>
\</servers\>
\<!-- 所有 Maven 依赖下载都走公司私服 --\>
\<mirrors\>
    \<mirror\>
        \<id\>central\</id\>
        \<name\>central\</name\>
        \<url\>http://localhost:8081/repository/maven-public/\</url\>
        \<mirrorOf\>*\</mirrorOf\>
    \</mirror\>
\</mirrors\>

下面在第一部分最基础的配置部分,会用到这里的配置,只有两者的 id 一致才能起到作用。

使用 IDEA 或 Eclipse 的 IDE 时,可能还需要指定上面配置好的 settings.xml。

Nexus 私服配置

这是最基础的配置部分,这部分的作用就是控制所有 Maven 项目可以 deploy 到 Nexus 私服中,因此这部分几乎从一开始就会固定下来,所以这里可以直接使用固定版本号。

创建 company-nexus 目录,在其中创建 pom.xml 文件,内容如下:

\<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"\>
    \<modelVersion\>4.0.0\</modelVersion\>

    \<groupId\>com.company\</groupId\>
    \<artifactId\>company-nexus\</artifactId\>
    \<version\>1\</version\>
    \<packaging\>pom\</packaging\>

    \<name\>company-nexus\</name\>
    \<description\>公司 Nexus 基础配置\</description\>
    \<url\>http://localhost:8081/\</url\>

    \<!-- 发布配置 --\>
    \<distributionManagement\>
        \<!-- 快照版仓库 --\>
        \<snapshotRepository\>
            \<!-- 和 settings.xml 中的快照版 id 保持一致 --\>
            \<id\>nexus-snapshots\</id\>
            \<!-- Nexus 中快照版仓库的地址 --\>
            \<url\>http://localhost:8081/repository/maven-snapshots/\</url\>
        \</snapshotRepository\>
        \<!-- 发布版仓库 --\>
        \<repository\>
            \<!-- 和 settings.xml 中的发布版 id 保持一致 --\>
            \<id\>nexus-releases\</id\>
            \<!-- Nexus 中发布版仓库的地址 --\>
            \<url\>http://localhost:8081/repository/maven-releases/\</url\>
        \</repository\>
    \</distributionManagement\>
\</project\>

配置好后,可以直接执行 maven deploy 将上面的 pom 发布到私服中,产生的部分日志如下:

[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building company-nexus 1
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-install-plugin:2.4:install (default-install) @ company-nexus ---
[INFO] Installing F:\Liu\maven\company-nexus\pom.xml to F:\Liu\maven\repository\com\company\company-nexus\1\company-nexus-1.pom
[INFO] 
[INFO] --- maven-deploy-plugin:2.7:deploy (default-deploy) @ company-nexus ---
Uploading: http://localhost:8081/repository/maven-releases/com/company/company-nexus/1/company-nexus-1.pom
Uploaded: http://localhost:8081/repository/maven-releases/com/company/company-nexus/1/company-nexus-1.pom (2 KB at 9.6 KB/sec)
Downloading: http://localhost:8081/repository/maven-releases/com/company/company-nexus/maven-metadata.xml
[IJ]-1-METADATA_DOWNLOADED-[IJ]-path=F:\Liu\maven\repository\com\company\company-nexus\maven-metadata-nexus-releases.xml-[IJ]-artifactCoord=-[IJ]-error=Could not find metadata com.company:company-nexus/maven-metadata.xml in nexus-releases (http://localhost:8081/repository/maven-releases/)
Uploading: http://localhost:8081/repository/maven-releases/com/company/company-nexus/maven-metadata.xml
Uploaded: http://localhost:8081/repository/maven-releases/com/company/company-nexus/maven-metadata.xml (296 B at 2.8 KB/sec)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.285 s
[INFO] Finished at: 2020-01-29T23:00:51+08:00
[INFO] Final Memory: 8M/40M
[INFO] ------------------------------------------------------------------------

因为是 release 版本(不含 SNAPSHOT),所以可以在 Nexus 从 maven-releases 下看到:

简单使用演示

有了最基础配置后,以后其他项目只要想发布到私服,就可以将这里的 pom 设置为 parent,例如:

\<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"\>
    \<modelVersion\>4.0.0\</modelVersion\>
    \<parent\>
        \<groupId\>com.company\</groupId\>
        \<artifactId\>company-nexus\</artifactId\>
        \<version\>1\</version\>
    \</parent\>
    \<artifactId\>company-demo\</artifactId\>
    \<version\>1.0.0-SNAPSHOT\</version\>

    \<dependencies\>
        \<dependency\>
            \<groupId\>tk.mybatis\</groupId\>
            \<artifactId\>mapper\</artifactId\>
            \<version\>4.1.5\</version\>
        \</dependency\>
    \</dependencies\>
\</project\>

使用 parent 继承时,一定要能区分 Maven 子模块和 Parent 继承的关系,详细内容参考:

Maven 的聚合(多模块)和 Parent 继承

此时在该项目执行 maven deploy 也可以发布到私服中。因为是快照版,所以能从 maven-snapshots 下面看到该文件:

<version> 了。

三方库依赖配置

这里从最常见的 Spring 以及 Spring Boot 依赖入手,开始控制所有常用的第三方依赖(根据自己需要添加)。

得益于 Spring,尤其是 Spring Boot 对第三方集成提供的 starter。Spring Boot 对可能用到的第三方依赖提供了很好的版本控制,我们就依靠 Spring 提供的 bom 和 dependencies 来实现我们自己的基础依赖。

这里选择 Spring 和 Spring Boot 的最新版本,这两个依赖分别如下:

1. Spring Framework (Bill of Materials) » 5.2.3.RELEASE

2. 2.2.4.RELEASE

这两个依赖分别管理了 21 个和 603 个依赖的版本,通过这两个就能直接帮我们控制 624 个依赖的版本。除了这两个基础之外,我们还要添加需要用到的其他第三方依赖,在选择依赖时,也要注意优先选择对方提供的 bom 依赖,例如:

  1. Dubbo BOM » 2.7.5Dubbo Dependencies BOM » 2.7.5
  2. 使用 tk.mybatis 各个独立依赖,可以选择 Mapper All » 4.1.5

从 Spring 和 Dubbo 两对依赖命名方式中,我们可以制定一个简单的规则。二方库依赖管理使用 xxx-bom 命名方式,对三方库的依赖使用 xxx-dependencies 命名方式。因此这里的三方库可以命名为 company-dependencies

company-dependencies 这个三方库 依赖的项目可以独立创建,也可以作为前面 company-nexus 的子模块进行创建,本文为了最终分享(源码)项目的简单,使用子模块来管理。

在 company-nexus 中添加子模块 company-dependencies,这个三方库在包含上述几个依赖的情况下,pom.xml 内容如下:

\<?xml version="1.0" encoding="UTF-8"?\>
\<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"\>
    \<parent\>
        \<artifactId\>company-nexus\</artifactId\>
        \<groupId\>com.company\</groupId\>
        \<version\>1\</version\>
    \</parent\>
    \<modelVersion\>4.0.0\</modelVersion\>

    \<artifactId\>company-dependencies\</artifactId\>
    \<version\>1.0.0-SNAPSHOT\</version\>
    \<packaging\>pom\</packaging\>

    \<properties\>
        \<spring.version\>5.2.3.RELEASE\</spring.version\>
        \<spring-boot.version\>2.2.4.RELEASE\</spring-boot.version\>
        \<dubbo.version\>2.7.5\</dubbo.version\>
        \<mapper.version\>4.1.5\</mapper.version\>
    \</properties\>

    \<dependencyManagement\>
        \<dependencies\>
            \<dependency\>
                \<groupId\>org.springframework\</groupId\>
                \<artifactId\>spring-framework-bom\</artifactId\>
                \<version\>${spring.version}\</version\>
                \<scope\>import\</scope\>
                \<type\>pom\</type\>
            \</dependency\>
            \<dependency\>
                \<groupId\>org.springframework.boot\</groupId\>
                \<artifactId\>spring-boot-dependencies\</artifactId\>
                \<version\>${spring-boot.version}\</version\>
                \<scope\>import\</scope\>
                \<type\>pom\</type\>
            \</dependency\>

            \<dependency\>
                \<groupId\>org.apache.dubbo\</groupId\>
                \<artifactId\>dubbo-bom\</artifactId\>
                \<version\>${dubbo.version}\</version\>
                \<scope\>import\</scope\>
                \<type\>pom\</type\>
            \</dependency\>
            \<dependency\>
                \<groupId\>org.apache.dubbo\</groupId\>
                \<artifactId\>dubbo-dependencies-bom\</artifactId\>
                \<version\>${dubbo.version}\</version\>
                \<scope\>import\</scope\>
                \<type\>pom\</type\>
            \</dependency\>

            \<dependency\>
                \<groupId\>tk.mybatis\</groupId\>
                \<artifactId\>mapper-all\</artifactId\>
                \<version\>${mapper.version}\</version\>
                \<scope\>import\</scope\>
                \<type\>pom\</type\>
            \</dependency\>

            \<!-- TODO 继续添加其他三方库 --\>
        \</dependencies\>
    \</dependencyManagement\>

\</project\>

这里是一个简单示例,真正使用时按照自己企业的需要添加三方库,通过这个 pom.xml 我们就可以管理三方库的版本了。当存在大量三方库以及各种 bom 和 dependencies 时,我们还需要关注下面这个问题。

如何处理依赖版本冲突?

spring-boot-dependenciesdubbo-bom 中都有对 com.google.code.gson » gson 的依赖,Spring 使用了 2.8.6,Dubbo 使用了 2.8.5,我们最终使用的版本是哪个呢?

通过在 IDEA 中右键 Show Effective Pom 查看最终完整的 pom 时,可以看到使用的 Gson 是 2.8.6 的版本,通过调整 spring-boot-dependenciesdubbo-bom 的先后顺序可以发现,版本有冲突时,配置在前的有效,如果你在管理依赖的最上面添加下面的依赖:

\<dependency\>
    \<groupId\>com.google.code.gson\</groupId\>
    \<artifactId\>gson\</artifactId\>
    \<version\>2.8.0\</version\>
\</dependency\>

最终使用的版本就是 2.8.0,因此当版本有冲突时,除了调整 bom 和 dependencies 顺序外,你还可以直接在最上面指定该依赖的具体版本。

当我们把常用的三方库版本控制后,通过 maven deploy 发布到 Nexus 私服,然后我们自己的项目就可以基于 company-dependencies 来使用第三方依赖了。

使用的方式可以是作为 parent 依赖,例如:

\<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"\>
    \<modelVersion\>4.0.0\</modelVersion\>
    \<parent\>
        \<groupId\>com.company\</groupId\>
        \<artifactId\>company-dependencies\</artifactId\>
        \<version\>1.0.0-SNAPSHOT\</version\>
    \</parent\>
    \<artifactId\>company-demo\</artifactId\>
    \<version\>1.0.0-SNAPSHOT\</version\>

    \<dependencies\>
        \<dependency\>
            \<groupId\>tk.mybatis\</groupId\>
            \<artifactId\>mapper-core\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>tk.mybatis\</groupId\>
            \<artifactId\>mapper-generator\</artifactId\>
        \</dependency\>
    \</dependencies\>
\</project\>

还可以通过 <dependencyManagement> 来控制版本:

\<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"\>
    \<modelVersion\>4.0.0\</modelVersion\>
    \<groupId\>com.company\</groupId\>
    \<artifactId\>company-demo\</artifactId\>
    \<version\>1.0.0-SNAPSHOT\</version\>

    \<dependencyManagement\>
        \<dependencies\>
            \<dependency\>
                \<groupId\>com.company\</groupId\>
                \<artifactId\>company-dependencies\</artifactId\>
                \<version\>1.0.0-SNAPSHOT\</version\>
                \<scope\>import\</scope\>
                \<type\>pom\</type\>
            \</dependency\>
        \</dependencies\>
    \</dependencyManagement\>

    \<dependencies\>
        \<dependency\>
            \<groupId\>tk.mybatis\</groupId\>
            \<artifactId\>mapper-core\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>tk.mybatis\</groupId\>
            \<artifactId\>mapper-generator\</artifactId\>
        \</dependency\>
    \</dependencies\>
\</project\>

在企业中,如果想要达到强制使用版本控制,parent 方式更好,利用 parent 分层控制不同的依赖,虽然缺失了一定的灵活性,但能更好的将依赖版本管理传递下去,接下来看下一层,二方库的依赖配置。

如何选择 release 和 snapshot?

三方库项目 company-dependencies 在初期不能完整添加所有第三方依赖时,可以先设置版本号为快照版,等所有依赖稳定时发布正式版,此时所有以此为 parent 的项目都需要升级 parent 版本号。

项目不稳定时,如果使用了 release 版本,那么每次添加第三方依赖都需要升级版本号,所有以此为 parent 的项目也不可避免的要升级 parent 版本号。

二方库依赖配置

在上面三方库的基础上,企业开发了大量的服务或模块时,这些服务或模块之间不可避免的会存在依赖关系,此时就需要管理二方库依赖了。

假设有以下三个服务(模块):

- company-common(模块)
- company-user(服务)
    - company-user-api(子模块)
    - company-user-service(子模块)
    - company-user-controller(子模块)
    - company-user-ui(子模块)
- company-crm(服务)
    - company-crm-api(子模块)
    - company-crm-service(子模块)
    - company-crm-controller(子模块)
    - company-crm-ui(子模块)

项目工程结构仅用于演示,和真实情况不完全一致。

其中 user 和 crm 都依赖 common,crm 还依赖 user。当模块少的时候如果直接写死在各自的 pom.xml 文件中也没什么问题,但是如果有几十个甚至上百个模块时,模块间的传递依赖都会导致当升级版本时无从下手,难道升级一个 A 模块,还需要手动修改几十个其他模块吗?

本文提供的方法,和手动修改几十个其他模块各有利弊,下面说说本文推荐的方式。

前面提到过二方库-bom 后缀,因此这里在 company-nexus 下面创建 company-bom 子模块(和前面原因一样,这里是为了最终的项目简单,你也可以创建独立项目,不使用子模块)。对应的 pom.xml 内容如下:

\<?xml version="1.0" encoding="UTF-8"?\>
\<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"\>
    \<parent\>
        \<artifactId\>company-nexus\</artifactId\>
        \<groupId\>com.company\</groupId\>
        \<version\>1\</version\>
    \</parent\>
    \<modelVersion\>4.0.0\</modelVersion\>

    \<artifactId\>company-bom\</artifactId\>
    \<version\>1-SNAPSHOT\</version\>
    \<packaging\>pom\</packaging\>

    \<properties\>
        \<common.version\>1.0.0-SNAPSHOT\</common.version\>
        \<crm.version\>1.0.0-SNAPSHOT\</crm.version\>
        \<user.version\>1.0.0-SNAPSHOT\</user.version\>
    \</properties\>

    \<dependencyManagement\>
        \<dependencies\>
            \<!-- common --\>
            \<dependency\>
                \<groupId\>com.company\</groupId\>
                \<artifactId\>company-common\</artifactId\>
                \<version\>${common.version}\</version\>
            \</dependency\>
            \<!-- crm --\>
            \<dependency\>
                \<groupId\>com.company\</groupId\>
                \<artifactId\>company-crm-api\</artifactId\>
                \<version\>${crm.version}\</version\>
            \</dependency\>
            \<dependency\>
                \<groupId\>com.company\</groupId\>
                \<artifactId\>company-crm-controller\</artifactId\>
                \<version\>${crm.version}\</version\>
            \</dependency\>
            \<dependency\>
                \<groupId\>com.company\</groupId\>
                \<artifactId\>company-crm-service\</artifactId\>
                \<version\>${crm.version}\</version\>
            \</dependency\>
            \<dependency\>
                \<groupId\>com.company\</groupId\>
                \<artifactId\>company-crm-ui\</artifactId\>
                \<version\>${crm.version}\</version\>
            \</dependency\>
            \<!-- user --\>
            \<dependency\>
                \<groupId\>com.company\</groupId\>
                \<artifactId\>company-user-api\</artifactId\>
                \<version\>${user.version}\</version\>
            \</dependency\>
            \<dependency\>
                \<groupId\>com.company\</groupId\>
                \<artifactId\>company-user-controller\</artifactId\>
                \<version\>${user.version}\</version\>
            \</dependency\>
            \<dependency\>
                \<groupId\>com.company\</groupId\>
                \<artifactId\>company-user-service\</artifactId\>
                \<version\>${user.version}\</version\>
            \</dependency\>
            \<dependency\>
                \<groupId\>com.company\</groupId\>
                \<artifactId\>company-user-ui\</artifactId\>
                \<version\>${user.version}\</version\>
            \</dependency\>
        \</dependencies\>
    \</dependencyManagement\>

\</project\>

在当前 bom 中,管理了所有企业内部服务(模块)的依赖。

此时,如果 company-crm-service 依赖了 company-user-api,就可以通过上面的 bom 来管理版本。例如:

\<?xml version="1.0" encoding="UTF-8"?\>
\<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"\>
    \<parent\>
        \<artifactId\>company-crm\</artifactId\>
        \<groupId\>com.company\</groupId\>
        \<version\>1.0.0-SNAPSHOT\</version\>
    \</parent\>
    \<modelVersion\>4.0.0\</modelVersion\>

    \<artifactId\>company-crm-service\</artifactId\>
    \<version\>1.0.0-SNAPSHOT\</version\>

    \<dependencyManagement\>
        \<dependencies\>
            \<dependency\>
                \<groupId\>com.company\</groupId\>
                \<artifactId\>company-bom\</artifactId\>
                \<version\>1-SNAPSHOT\</version\>
                \<scope\>import\</scope\>
                \<type\>pom\</type\>
            \</dependency\>
            \<dependency\>
                \<groupId\>com.company\</groupId\>
                \<artifactId\>company-dependencies\</artifactId\>
                \<version\>1.0.0-SNAPSHOT\</version\>
                \<scope\>import\</scope\>
                \<type\>pom\</type\>
            \</dependency\>
        \</dependencies\>
    \</dependencyManagement\>

    \<dependencies\>
        \<dependency\>
            \<groupId\>com.company\</groupId\>
            \<artifactId\>company-crm-api\</artifactId\>
            \<version\>${project.version}\</version\>
        \</dependency\>
        \<dependency\>
            \<groupId\>com.company\</groupId\>
            \<artifactId\>company-user-api\</artifactId\>
        \</dependency\>

        \<dependency\>
            \<groupId\>tk.mybatis\</groupId\>
            \<artifactId\>mapper-core\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>tk.mybatis\</groupId\>
            \<artifactId\>mapper-generator\</artifactId\>
        \</dependency\>
    \</dependencies\>

\</project\>

改成上面这种方式控制版本后,你可能也会发现一个问题,在 bom 中也管理了 company-crm 相关模块的版本,company-crm 又用到了 company-bom,似乎产生了一个不好的循环依赖。在这种情况下,就需要一定的规则规定来限制往 company-bom 中添加依赖的时机。

在开发 company-crm 初期,bom 中还没有(不能添加) CRM 的依赖版本控制,当 CRM 第一次 maven deploy 开始允许其他人使用时,才能添加到 bom 中,在这种顺序下,即是应有的操作,也是正确的操作。

这里简单举例只写了一个 company-bom,实际上当存在几十个上百个服务模块时,还可以根据功能、产品、项目、部门、项目组等不同维度拆分成更细致的 bom。上面这种方式可以按照这些维度对版本进行管理,而且简化了多个项目之间传递依赖时的版本不一致,全局控制的版本更统一。当某个版本升级时,修改快照版的 bom 即可。

上面这种方式已经能解决大量依赖版本不一致的问题,但是还存在一些不可避免的缺陷,缺陷的原因还是上面提到过的循环依赖,想象下面这种情况:

当 bom 想发布 release 版本时,所有服务模块可能都是快照版,如果服务模块发布 release 版本,但是他引用的 bom 还是快照版(理论上 release 版本中不应该存在其他快照版依赖),这个循环依赖导致 bom 无法发布真正的 release 版本。

针对上面这种情况,有两种处理方式,既然必须进行取舍,而大量内部依赖时版本不统一的问题更愁人,因此可以选择永远使用快照版的 bom 版本,bom 永远是快照版,有大的升级时可以升级版本,但是不会有 release 版本。bom 中控制的其他服务模块可以根据需要发布 release 版本。在这种情况下 bom 不可能频繁升级小版本(x.y.z-SNAPSHOT 中的 y 和 z),因此版本号可以直接简化为 1-SNAPSHOT,升级后直接 2-SNAPSHOT,按照产品迭代更新来说,一年也不会出现太多大版本的升级。

另外一种处理方式就只是为了所有都能发布 release 版本,在使用上会有更大的限制。这种情况下,bom 中包含的服务模块不能使用该 bom,那么该 bom 就是为了方便其他功能、产品、项目、部门、项目组等不同维度去使用的,比如下面的简单例子:

A 部门开发了很多服务,为了让其他部门知道本部门所有服务版本号的依赖关系,可以创建一个 company-a-bom,包含了 A 部门提供的所有服务依赖,但是 A 部门内部不使用该 bom,部门内部的依赖还通过直接指定版本号使用。

上面这个例子是方便了其他部门的消费者,但是对本部门来说没有明显的好处,反而增加了一些工作量,如果公司就一个部门或很少的部门,这种方式用途就不明显了。所以本文推荐前一种方式。

继续引申到下一节,在上面的 company-crm-service 中,添加了 <dependencyManagement>,如下所示:

\<dependencyManagement\>
    \<dependencies\>
        \<dependency\>
            \<groupId\>com.company\</groupId\>
            \<artifactId\>company-bom\</artifactId\>
            \<version\>1-SNAPSHOT\</version\>
            \<scope\>import\</scope\>
            \<type\>pom\</type\>
        \</dependency\>
        \<dependency\>
            \<groupId\>com.company\</groupId\>
            \<artifactId\>company-dependencies\</artifactId\>
            \<version\>1.0.0-SNAPSHOT\</version\>
            \<scope\>import\</scope\>
            \<type\>pom\</type\>
        \</dependency\>
    \</dependencies\>
\</dependencyManagement\>

按照这种用法,api, controller, service, ui 可能需要配置上面的依赖管理,大量复制粘贴这段配置也会导致当增加 bom 或者修改 bom 版本时,所有子模块都需要改动。而且 api、controller、service、ui 这种模块可能都包含了相同的基础依赖,每个服务的子模块都要重复一遍,这都是隐藏的风险。为了解决这个问题,我们在 company-bom 和 company-dependencies 基础上在增加两个层次来简化配置。

子模块依赖配置

到这里时,我们有了以下几个低层次的依赖配置:

  • company-nexus:私服基础配置,方便 deploy 到 Nexus 私服
  • company-dependencies:第三方依赖管理,包含常见和特有的三方库依赖
  • company-bom:二方库依赖管理,企业内部服务依赖版本管理

上一节使用的时候需要每个项目都单独配置 <dependencyManagement> 使用,在这里为了简化这个操作,我们先增加一层封装。这里起名为 company-parent,仍然作为子模块创建在 company-nexus 中。其中的 pom.xml 内容如下:

\<?xml version="1.0" encoding="UTF-8"?\>
\<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"\>
    \<parent\>
        \<artifactId\>company-nexus\</artifactId\>
        \<groupId\>com.company\</groupId\>
        \<version\>1\</version\>
    \</parent\>
    \<modelVersion\>4.0.0\</modelVersion\>

    \<artifactId\>company-parent\</artifactId\>
    \<version\>1-SNAPSHOT\</version\>
    \<packaging\>pom\</packaging\>

    \<properties\>
        \<company-bom.version\>1-SNAPSHOT\</company-bom.version\>
        \<company-dependencies.version\>1.0.0-SNAPSHOT\</company-dependencies.version\>
    \</properties\>

    \<dependencyManagement\>
        \<dependencies\>
            \<dependency\>
                \<groupId\>com.company\</groupId\>
                \<artifactId\>company-bom\</artifactId\>
                \<version\>${company-bom.version}\</version\>
                \<scope\>import\</scope\>
                \<type\>pom\</type\>
            \</dependency\>
            \<dependency\>
                \<groupId\>com.company\</groupId\>
                \<artifactId\>company-dependencies\</artifactId\>
                \<version\>${company-dependencies.version}\</version\>
                \<scope\>import\</scope\>
                \<type\>pom\</type\>
            \</dependency\>
        \</dependencies\>
    \</dependencyManagement\>

\</project\>

简单来说,这儿和前面的 company-crm-service 内容很类似,提取 company-parent 就是为了将这部分配置单独管理起来,这样后续子模块以这里的 company-parent 作为父依赖时就不需要再重复该内容了。这种方式已经解决了前面提到的第一个问题。还剩一个,如何解决 api, controller, service, ui 这种模块可能都包含的相同的基础依赖。解决的办法就是针对 api, controller, service, ui 各封装一层。在 company-parent 中依次添加下面 4 个子模块:

company-parent-api:管理 api 层公共依赖

  • company-parent-controller:管理 controller 层公共依赖
  • company-parent-service:管理 service 层公共依赖
  • company-parent-ui:管理 ui 层公共依赖

由于不同企业子模块拆分形式不同,这里仅以 service 分层进行简单的演示。

company-parent-service 对应的 pom.xml 示例如下:

\<?xml version="1.0" encoding="UTF-8"?\>
\<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"\>
    \<parent\>
        \<artifactId\>company-parent\</artifactId\>
        \<groupId\>com.company\</groupId\>
        \<version\>1-SNAPSHOT\</version\>
    \</parent\>
    \<modelVersion\>4.0.0\</modelVersion\>

    \<artifactId\>company-parent-service\</artifactId\>
    \<packaging\>pom\</packaging\>

    \<dependencies\>
        \<!-- 日志 依赖 --\>
        \<dependency\>
            \<groupId\>org.slf4j\</groupId\>
            \<artifactId\>slf4j-api\</artifactId\>
        \</dependency\>

        \<!-- Spring 依赖 --\>
        \<dependency\>
            \<groupId\>org.springframework\</groupId\>
            \<artifactId\>spring-context-support\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>org.springframework\</groupId\>
            \<artifactId\>spring-jdbc\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>org.springframework\</groupId\>
            \<artifactId\>spring-tx\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>org.springframework\</groupId\>
            \<artifactId\>spring-aop\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>org.springframework\</groupId\>
            \<artifactId\>spring-aspects\</artifactId\>
        \</dependency\>

        \<!-- Mapper 依赖 --\>
        \<dependency\>
            \<groupId\>tk.mybatis\</groupId\>
            \<artifactId\>mapper-core\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>tk.mybatis\</groupId\>
            \<artifactId\>mapper-base\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>tk.mybatis\</groupId\>
            \<artifactId\>mapper-extra\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>tk.mybatis\</groupId\>
            \<artifactId\>mapper-spring\</artifactId\>
        \</dependency\>

        \<!-- 数据库驱动 --\>
        \<dependency\>
            \<groupId\>mysql\</groupId\>
            \<artifactId\>mysql-connector-java\</artifactId\>
        \</dependency\>

        \<!-- 数据库连接池 --\>
        \<dependency\>
            \<groupId\>com.zaxxer\</groupId\>
            \<artifactId\>HikariCP\</artifactId\>
        \</dependency\>

        \<!-- Dubbo --\>
        \<dependency\>
            \<groupId\>org.apache.dubbo\</groupId\>
            \<artifactId\>dubbo-common\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>org.apache.dubbo\</groupId\>
            \<artifactId\>dubbo-config-spring\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>org.apache.dubbo\</groupId\>
            \<artifactId\>dubbo-remoting-netty\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>org.apache.dubbo\</groupId\>
            \<artifactId\>dubbo-rpc-dubbo\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>org.apache.dubbo\</groupId\>
            \<artifactId\>dubbo-registry-zookeeper\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>org.apache.dubbo\</groupId\>
            \<artifactId\>dubbo-serialization-hessian2\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>org.apache.dubbo\</groupId\>
            \<artifactId\>dubbo-metadata-report-zookeeper\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>org.apache.dubbo\</groupId\>
            \<artifactId\>dubbo-configcenter-zookeeper\</artifactId\>
        \</dependency\>

        \<!-- zookeeper --\>
        \<dependency\>
            \<groupId\>org.apache.zookeeper\</groupId\>
            \<artifactId\>zookeeper\</artifactId\>
        \</dependency\>
        \<dependency\>
            \<groupId\>org.apache.curator\</groupId\>
            \<artifactId\>curator-framework\</artifactId\>
        \</dependency\>
    \</dependencies\>

\</project\>

上面只是简单的示范,真正应用时要根据自己企业的项目来设计。当有了这些子模块对应的 parent 项目后,我们来改造前面的 company-crm-service,改造后的 pom.xml 如下:

\<?xml version="1.0" encoding="UTF-8"?\>
\<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"\>
    \<parent\>
        \<artifactId\>company-parent-service\</artifactId\>
        \<groupId\>com.company\</groupId\>
        \<version\>1-SNAPSHOT\</version\>
    \</parent\>
    \<modelVersion\>4.0.0\</modelVersion\>

    \<artifactId\>company-crm-service\</artifactId\>
    \<version\>1.0.0-SNAPSHOT\</version\>

    \<dependencies\>
        \<dependency\>
            \<groupId\>com.company\</groupId\>
            \<artifactId\>company-crm-api\</artifactId\>
            \<version\>${project.version}\</version\>
        \</dependency\>
        \<dependency\>
            \<groupId\>com.company\</groupId\>
            \<artifactId\>company-user-api\</artifactId\>
        \</dependency\>
    \</dependencies\>

\</project\>

可以看到,改造后只需要添加 company-crm-service 自己特有的依赖即可,而且由于 company-bom 的存在,这里依赖 company-user-api 时也不需要指定版本号。

将来 company-user-api 版本升级的时候,只需要在 company-bom 中更新版本,company-crm-service 重新构建即可应用到最新版本。如果 company-user-api 存在不兼容的 api 改动,company-crm-service 编译时就会通过错误信息看出来,虽然无法正常编译了,但是也尽可能快的把错误暴露了。

总结

到这里时,我们有了以下几个低层次的依赖配置:

company-nexus:私服基础配置,方便 deploy 到 Nexus 私服

  • company-dependencies:第三方依赖管理,包含常见和特有的三方库依赖
  • company-bom:二方库依赖管理,企业内部服务依赖版本管理
  • company-parent:包含上面两个依赖管理
    • company-parent-api:管理 api 层公共依赖
    • company-parent-controller:管理 controller 层公共依赖
    • company-parent-service:管理 service 层公共依赖
    • company-parent-ui:管理 ui 层公共依赖

有了上面这些标准化的项目工程结构时,不仅仅可以方便的管理各种库的依赖,利用 Maven 原型还可以方便得快速生成新的项目。这些新的项目结构有特殊的 parent 继承关系(不理解的一定要看 Maven 的聚合(多模块)和 Parent 继承),以提到过的 company-crm 项目为例,该项目结构如下:

company-crm(服务)

  • company-crm-api(子模块)
  • company-crm-service(子模块)
  • company-crm-controller(子模块)
  • company-crm-ui(子模块)

下面挨个来看改造后的各个模块的 pom.xml 可能是什么样子。

company-crm
\<?xml version="1.0" encoding="UTF-8"?\>
\<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"\>
    \<parent\>
        \<artifactId\>company-nexus\</artifactId\>
        \<groupId\>com.company\</groupId\>
        \<version\>1\</version\>
    \</parent\>
    \<modelVersion\>4.0.0\</modelVersion\>

    \<artifactId\>company-crm\</artifactId\>
    \<version\>1.0.0-SNAPSHOT\</version\>
    \<packaging\>pom\</packaging\>

    \<modules\>
        \<module\>company-crm-api\</module\>
        \<module\>company-crm-service\</module\>
        \<module\>company-crm-controller\</module\>
        \<module\>company-crm-ui\</module\>
    \</modules\>
\</project\>

当前项目包含了 4 个子模块,本身没有代码,因此 <parent> 使用 company-nexus 可以 deploy 就行。

company-crm-api
\<?xml version="1.0" encoding="UTF-8"?\>
\<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"\>
    \<parent\>
        \<artifactId\>company-parent-api\</artifactId\>
        \<groupId\>com.company\</groupId\>
        \<version\>1-SNAPSHOT\</version\>
    \</parent\>
    \<modelVersion\>4.0.0\</modelVersion\>
    \<artifactId\>company-crm-api\</artifactId\>
    \<version\>1.0.0-SNAPSHOT\</version\>
\</project\>

注意这里的 <parent> 是 company-parent-api,不是上面的 company-crm,所以在这个 pom.xml 中必须指定自己的 <version> 版本号,否则会和 <parent> 的 1-SNAPSHOT 一样。

company-crm-service
\<?xml version="1.0" encoding="UTF-8"?\>
\<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"\>
    \<parent\>
        \<artifactId\>company-parent-service\</artifactId\>
        \<groupId\>com.company\</groupId\>
        \<version\>1-SNAPSHOT\</version\>
    \</parent\>
    \<modelVersion\>4.0.0\</modelVersion\>
    \<artifactId\>company-crm-service\</artifactId\>
    \<version\>1.0.0-SNAPSHOT\</version\>

    \<dependencies\>
        \<dependency\>
            \<groupId\>com.company\</groupId\>
            \<artifactId\>company-crm-api\</artifactId\>
            \<version\>${project.version}\</version\>
        \</dependency\>
        \<dependency\>
            \<groupId\>com.company\</groupId\>
            \<artifactId\>company-user-api\</artifactId\>
        \</dependency\>
    \</dependencies\>
\</project\>

这里和上面的 api 类似,要注意版本号。一方库 company-crm-api 的版本号使用 ${project.version}

company-crm-controller
\<?xml version="1.0" encoding="UTF-8"?\>
\<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"\>
    \<parent\>
        \<artifactId\>company-parent-controller\</artifactId\>
        \<groupId\>com.company\</groupId\>
        \<version\>1-SNAPSHOT\</version\>
    \</parent\>
    \<modelVersion\>4.0.0\</modelVersion\>
    \<artifactId\>company-crm-controller\</artifactId\>
    \<version\>1.0.0-SNAPSHOT\</version\>
    \<dependencies\>
        \<dependency\>
            \<groupId\>com.company\</groupId\>
            \<artifactId\>company-crm-api\</artifactId\>
            \<version\>${project.version}\</version\>
        \</dependency\>
    \</dependencies\>
\</project\>

这里和上面的 controller 类似。

company-crm-ui
\<?xml version="1.0" encoding="UTF-8"?\>
\<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"\>
    \<parent\>
        \<artifactId\>company-parent-ui\</artifactId\>
        \<groupId\>com.company\</groupId\>
        \<version\>1-SNAPSHOT\</version\>
    \</parent\>
    \<modelVersion\>4.0.0\</modelVersion\>
    \<artifactId\>company-crm-ui\</artifactId\>
    \<version\>1.0.0-SNAPSHOT\</version\>
\</project\>

和上面的 api 类似。

以上就是本文的内容,Maven 依赖管理层次结构设计经过多年真正实践,应用效果很好,但是能否更广泛的应用就需要读者结合自身情况来调整适应。对文中有更好想法或疑问的可以留言进行交流。


欢迎关注我的公众号,回复关键字“大礼包” ,将会有大礼相送!!! 祝各位面试成功!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洲洋的编程课堂

祝你找到满意的工作

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值