Spring Modulith 迎来 2.1 GA:用模块化架构驯服膨胀的 Spring Boot 项目

2026-06-11 25 预计阅读时间: 1 分钟
来源: spring.io AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:8 分钟

Spring Boot 项目从小型单体一路膨胀到几十个包、上百个类,是很多团队的日常。边界模糊、依赖纠缠、改一处牵一片——这些问题不是靠"多写注释"能解决的。Spring Modulith 正是为这种场景而生:在 Spring Boot 单体内引入显式模块边界,让架构约束变成可验证、可文档化的代码事实。

近期 Spring Modulith 同时发布了 2.1 GA2.0.71.4.12 三个版本线。2.1 GA 是当前主线的新特性版本,2.0.7 与 1.4.12 分别为各自版本线的维护更新。如果你已经在用 2.0 或 1.4,可以平滑升级;如果准备新引入,直接从 2.1 开始。

模块不是包,是边界契约

Spring Modulith 的核心思路:一个"模块"不只是 Java 包的分组,而是有明确 API 面、内部实现隔离、跨模块通信规则的独立单元。

每个模块由三部分构成:

  • API 包module.api):对外暴露的类型,其他模块可以依赖。
  • 内部包module.internal):实现细节,其他模块禁止访问。
  • 事件module.event):模块间通过 Spring ApplicationEvent 通信,而非直接调用。

这种划分不是约定——框架提供了验证机制,在测试和构建时自动检查是否有模块越界访问。

快速搭建一个模块化项目

下面用一个订单系统示例演示 Spring Modulith 的核心用法。先创建项目结构:

src/main/java/com/example/
├── order/
│   ├── api/        ← OrderService, OrderDTO
│   ├── internal/   ← OrderRepository, OrderServiceImpl
│   └── event/      ← OrderCompletedEvent
├── inventory/
│   ├── api/
│   ├── internal/
│   └── event/
└── Application.java

引入依赖

<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.modulith</groupId>
        <artifactId>spring-modulith-starter</artifactId>
        <version>2.1.0</version>
    </dependency>
    <!-- 测试时验证模块边界 -->
    <dependency>
        <groupId>org.springframework.modulith</groupId>
        <artifactId>spring-modulith-starter-test</artifactId>
        <version>2.1.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

定义模块

// com.example.order.api.OrderService
public interface OrderService {
    String placeOrder(OrderDTO dto);
}

// com.example.order.internal.OrderServiceImpl
@Component
class OrderServiceImpl implements OrderService {

    private final OrderRepository repo;
    private final ApplicationEventPublisher events;

    OrderServiceImpl(OrderRepository repo,
                     ApplicationEventPublisher events) {
        this.repo = repo;
        this.events = events;
    }

    @Override
    public String placeOrder(OrderDTO dto) {
        var id = repo.save(new Order(dto.product(), dto.qty()));
        events.publishEvent(new OrderCompletedEvent(id, dto.product(), dto.qty()));
        return id;
    }
}
// com.example.order.event.OrderCompletedEvent
public record OrderCompletedEvent(String orderId, String product, int qty) {}
// com.example.inventory.internal.InventoryListener
@Component
class InventoryListener {

    @EventListener
    void on(OrderCompletedEvent event) {
        // 扣减库存——通过事件通信,不直接依赖 order.internal
        deduct(event.product(), event.qty());
    }

    private void deduct(String product, int qty) { /* ... */ }
}

关键点:inventory 模块只依赖 order.api 包中的类型,通过事件响应订单完成。它绝不触碰 order.internal——框架会替你守门。

验证边界:让越界依赖在测试阶段报错

Spring Modulith 提供了 ModuleTests 抽象类,一行代码就能验证所有模块的边界合规性:

// src/test/java/com/example/ModularityTests.java
class ModularityTests extends ModuleTests {

    @Test
    void verifiesModuleStructure() {
        // 验证所有模块:API 包不被内部包反向依赖,
        // 模块间不出现非法跨包引用
        verifyModuleStructure();
    }
}

如果有人不小心在 inventory.internal 里 import 了 order.internal.OrderRepository,这个测试会直接失败并输出清晰的越界报告。这比人工 Code Review 查依赖可靠得多。

生成模块文档与可视化

模块边界不只是代码约束,还应该让团队看得见。Spring Modulith 内建了文档生成能力:

// 在测试中生成 PlantUML 和 Asciidoc 文档
class DocumentationTests extends ModuleTests {

    @Test
    void writeDocumentation() {
        // 生成模块依赖图(PlantUML 格式)
        moduleDocumentation().writeModuleCanvases()
                              .writeIndividualModulesAsPlantUml()
                              .writeAllModulesAsPlantUml();
    }
}

也可以通过 Maven 插件在构建时自动生成:

<plugin>
    <groupId>org.springframework.modulith</groupId>
    <artifactId>spring-modulith-maven-plugin</artifactId>
    <version>2.1.0</version>
    <executions>
        <execution>
            <goals>
                <goal>generate-documentation</goal>
            </goals>
        </execution>
    </executions>
</plugin>

生成的文档包含每个模块的 API 类型列表、事件列表、依赖关系图。新成员加入团队时,看文档比翻代码快十倍。

2.1 版本升级要点

对于已经在使用 Spring Modulith 的项目,升级时注意以下几点:

  • 从 2.0 升到 2.1:主要是新特性增量,API 兼容。检查 spring-modulith-starterspring-modulith-starter-test 版本号同步更新即可。
  • 从 1.4 升到 2.x:2.0 线有较大 API 变动(模块检测机制、事件发布模型重构),建议先在分支上跑 ModuleTests,确认所有边界验证通过后再合并。
  • 版本线选择:1.4.12 是 1.x 的最后一个维护版本,不会再有新特性。新项目直接用 2.1;老项目如果暂时不想大改,1.4.12 依然稳定可用,但应规划向 2.x 迁移。

采用建议与取舍清单

Spring Modulith 不是万能药。它最适合的场景是:单体项目已经有一定规模,团队对模块边界有共识但执行力不足。以下清单帮你判断是否值得引入:

检查项 适合引入 暂缓引入
包数量 >30 个包,依赖关系难以口述 <15 个包,结构一目了然
跨模块通信 目前靠直接调用内部类 已经在用事件或接口解耦
团队规模 3+ 人同时改同一项目 1-2 人,口头约定够用
测试习惯 有集成测试基础 测试覆盖率低,先补测试
迁移意愿 能接受渐进式拆包 项目即将重写或退役

引入路径建议:先在一个新模块或边界最清晰的模块上试点,跑通 ModuleTests 和文档生成,再逐步覆盖其他模块。不要一次性重构整个项目——Spring Modulith 本身就支持渐进式采用。

最后提醒:Spring Modulith 的模块验证在编译后、运行前生效。如果你用 Lombok 生成代码或用 MapStruct 做 DTO 映射,确保生成的类落在正确的包里,否则验证可能误报。遇到这种情况可以用 @ModulithicallowedDependencies 属性显式声明例外。


相关推荐