分模块设计与开发
1. 分模块设计的核心概念
A. 模块(Module)的定义
一个模块通常是一个独立的代码单元,它具有单一的、明确的职责。在 Maven 或 Gradle 项目中,一个模块通常对应一个子项目(sub-project)。
B. 核心目标
-
解耦 (Decoupling): 降低模块之间的依赖程度。
-
复用 (Reusability): 将通用功能提取出来,供多个模块共享。
-
可维护性 (Maintainability): 模块独立,修改一个模块不轻易影响其他模块。
-
团队协作 (Collaboration): 不同的团队或个人可以并行开发不同的模块。
🧱 2. 典型的模块划分方法
在设计时,模块可以根据技术层次(三层架构)或业务功能(微服务)进行划分。
方案一:基于技术层次(传统三层架构)
这种划分适用于中小型单体应用,便于管理代码结构。
| 模块名称 | 职责定位 | 典型内容 |
xxx-api (或 xxx-interface) | 定义接口和模型。 存放 DTO/VO/Entity 等数据模型,以及供外部调用的服务接口。不包含任何业务实现。 | Java 接口 (UserService.java)、数据传输对象 (UserDTO.java)。 |
xxx-service | 业务逻辑实现层。 实现 xxx-api 定义的接口,包含核心业务逻辑、事务处理等。 | UserServiceImpl.java、业务验证代码。 |
xxx-dao (或 xxx-repository) | 数据访问层。 负责与数据库的交互,封装 SQL/持久化操作。 | MyBatis Mapper 接口、JPA Repository 接口。 |
xxx-web (或 xxx-controller) | 用户接口层。 接收和响应 HTTP 请求,调用 xxx-service 层的接口。 | Spring MVC 控制器 (UserController.java)。 |
方案二:基于业务功能(微服务/领域驱动)
这种划分更适用于大型、复杂的企业应用,追求极致的解耦和独立部署。
| 模块名称 | 职责定位 | 典型内容 |
order-service | 仅处理所有与订单相关的业务逻辑。 | OrderController, OrderService, OrderMapper 等全部相关代码。 |
user-service | 仅处理所有与用户相关的业务逻辑(注册、登录、权限)。 | UserController, UserService, UserRepository 等全部相关代码。 |
common-utils | 存放所有业务都需要的通用工具。 | 日期处理工具、加密工具、自定义异常类等。 |
🛠️ 3. 分模块开发的关键实践
实践 A: 依赖管理
-
单向依赖: 模块之间的依赖关系应该是清晰且单向的。例如,
order-service可以依赖user-service的api模块,但不能反过来。 -
父子项目 (Parent-Child): 使用 Maven 或 Gradle 的父项目来统一管理所有子模块的公共依赖版本(使用
<dependencyManagement>),避免版本冲突。
实践 B: 接口隔离原则 (Interface Segregation Principle)
-
如果两个模块需要通信,只让它们依赖彼此的 API/Interface 模块,而不是依赖具体的实现模块(
xxx-service)。这样,即使修改了实现细节,也不会影响依赖方。
实践 C: 独立测试
-
每个模块都应具备独立的单元测试和集成测试能力,确保在修改一个模块后,不会引入新的回归错误。
实践 D: 服务的发现与通信(在微服务中)
-
如果采用业务划分,模块间通常通过 HTTP/RPC(如 Feign, gRPC)进行网络通信,并通过 服务注册中心(如 Eureka, Nacos)进行服务的发现和负载均衡。
总结: 分模块设计是将“大问题”分解为“小问题”的过程,通过清晰的边界和依赖关系,极大地提高了项目的质量和开发效率。
继承与聚合
1. 继承(Inheritance)
继承主要用于解决 配置共享 和 版本统一 的问题。

概念
通过继承,子模块可以从父 POM 中获取通用的配置信息,从而避免在每个子模块中重复定义。

核心机制
| 元素 | 作用 | 解释 |
子模块:<parent> | 建立继承关系 | 子模块的 pom.xml 中必须使用 <parent> 标签指向父模块的 groupId, artifactId 和 version。 |
父模块:<dependencyManagement> | 统一依赖版本 | 父 POM 在 <dependencyManagement> 中定义依赖的版本号。子模块引用该依赖时,无需指定版本号,版本由父 POM 继承而来。 |
父模块:<pluginManagement> | 统一插件配置 | 类似于依赖管理,统一管理插件的版本和通用配置。 |
父模块:<properties> | 共享变量 | 定义公共变量(如 JDK 版本、Spring 版本),供所有子模块引用。 |
2. 聚合(Aggregation)
聚合主要用于解决 统一构建 的问题。
概念
通过聚合,一个父 POM 可以将多个子模块组织在一起,形成一个单一的构建单元。当你对父 POM 执行构建命令(如 mvn install)时,Maven 会自动依次构建所有被聚合的子模块。
核心机制
| 元素 | 作用 | 解释 |
父模块:<packaging>pom</packaging> | 标记聚合/父项目 | 聚合模块的打包方式必须是 pom。这意味着父模块本身不产生任何可执行文件(如 JAR 或 WAR)。 |
父模块:<modules> | 列出子模块 | 父 POM 使用 <modules> 标签列出所有需要被聚合和构建的子模块的相对目录。 |

聚合的优势
执行 mvn install 命令时:
-
Maven 找到父 POM 中的
<modules>列表。 -
它会根据模块的依赖关系(如果
service依赖api),自动确定正确的构建顺序。 -
然后 Maven 会按照这个顺序,从上到下依次执行
clean install命令。
3. 继承与聚合的关系

在实际的多模块项目中,继承和聚合通常是结合在一起使用的:
-
一个父 POM(
<packaging>pom</packaging>)同时负责:-
聚合: 通过
<modules>管理所有子模块的构建顺序。 -
继承: 通过
<dependencyManagement>和<properties>为所有子模块提供统一的配置。
-
-
所有子模块(
<packaging>jar</packaging>或war)都通过<parent>标签继承自这个父 POM。
这种结构确保了整个项目既有统一的构建流程(聚合),又有统一的技术配置(继承),是标准 Maven 多模块项目的最佳实践。
4. Maven 中的版本锁定机制
核心标签:<dependencyManagement>
<dependencyManagement> 标签位于父 POM 中,专门用于集中管理项目所有依赖的版本号。
-
定义版本,但不引入依赖: 当你在
<dependencyManagement>中声明一个依赖时,它不会将该依赖实际引入到项目中。它只是定义了一个版本查找表。 -
子模块继承: 所有子模块都继承这个版本表。
-
子模块使用: 子模块在自己的
<dependencies>中再次声明需要的依赖时,无需填写版本号。Maven 会自动从父 POM 的<dependencyManagement>中查找并使用锁定的版本。
为什么需要版本锁定?
-
避免冲突: 如果项目中有多个模块都使用了
log4j,但在不同模块中版本不一致(一个用2.17.0,一个用2.19.0),可能会导致运行时错误或依赖地狱。 -
统一升级: 当需要升级某个核心依赖(如 Spring 版本)时,只需要修改父 POM 中的一个版本号,所有子模块都会自动同步。
-
清晰可见: 所有关键依赖的版本号集中在一个地方管理,便于审计和维护。
父模块中的自定义属性

5. 版本锁定与依赖引入的区别
| 机制 | 标签 | 作用 | 结果 |
| 版本锁定 | <dependencyManagement> (在父 POM) | 声明依赖的版本,供子模块继承和查找。 | 不引入实际的 JAR 包到 Classpath。 |
| 依赖引入 | <dependencies> (在子 POM) | 声明模块实际需要的依赖。 | 引入实际的 JAR 包到 Classpath。 |

💡 Spring Boot 的版本锁定
对于 Spring Boot 项目,其版本锁定机制更为强大和自动化:
当你继承 Spring Boot 的父级项目 spring-boot-starter-parent 时,它内部已经包含了庞大的 <dependencyManagement> 块,默认就锁定了几乎所有主流依赖的版本(如 Spring Framework、Tomcat、Thymeleaf、Logback 等)。
什么是私服 (Private Repository)?

私服充当了开发者和外部公共仓库(如 Maven Central)之间的代理和缓存层。
常用工具
实现私服的常见软件有:
-
Nexus Repository Manager (Sonatype Nexus)
-
JFrog Artifactory
私服的三大核心功能
-
代理(Proxy): 代理外部的公共仓库(如 Maven Central、JCenter)。
-
缓存(Cache): 存储从外部下载的依赖,供内部快速重用。
-
宿主(Host): 存储和发布企业内部自己开发的、需要共享的模块或组件。
为什么企业需要使用私服?
使用私服带来了多方面的优势,尤其对于大型团队和持续集成/部署 (CI/CD) 环境至关重要。
1. 提升构建速度和稳定性
-
加速构建: 第一次下载依赖后,私服会将其缓存到本地服务器。团队中的其他成员再次请求时,直接从内网的私服下载,速度远快于从国外公共仓库下载。
-
网络稳定: 避免因公共仓库网络波动或限制而导致的构建失败。
2. 安全性与合规性
-
安全扫描: 可以集中管理依赖,方便进行安全漏洞扫描(如检测 Log4j 漏洞),避免使用带有已知安全问题的组件。
-
隔离外部风险: 只有私服需要连接外部网络,开发机可以完全隔离在内网中,提高了安全级别。
-
审核与控制: 可以阻止团队成员下载和使用某些不符合企业规范的依赖。
3. 共享内部组件(宿主功能)
-
内部协作: 这是实现 分模块开发 和 微服务 的关键。企业内部团队开发的公共库(如
common-utils、user-api)可以发布到私服上。 -
其他项目可以直接通过依赖的方式,从私服拉取这些内部组件,实现组件的快速复用。
🛠️ 私服的工作流程(以 Maven 为例)

release版本:发行版本
snapshot版本:快照版本
-
配置: 开发者修改 Maven 的
settings.xml文件,将默认的公共仓库地址替换为私服的地址。 -
请求: 当开发者执行
mvn compile时,Maven 向内部的私服请求所需的依赖。 -
私服处理:
-
命中缓存? 如果私服的缓存中存在该依赖,直接返回给开发者。
-
未命中? 私服作为代理,会去连接配置好的外部公共仓库(如 Maven Central)下载依赖。
-
缓存: 下载成功后,私服将依赖缓存起来。
-
返回: 将依赖返回给开发者。
-
-
发布: 当开发者需要发布自己的内部组件时,执行
mvn deploy,组件会被上传到私服的宿主仓库中。
1278

被折叠的 条评论
为什么被折叠?



