Fundamentals

Spring Modulith 支持开发人员在 Spring Boot 应用程序中实现逻辑模块。它允许他们应用结构验证、记录模块安排、对单个模块运行集成测试、在运行时观察模块的交互,并且通常以松散耦合的方式实现模块交互。本节将讨论开发人员在深入了解技术支持之前需要了解的基本概念。

Application modules

在 Spring Boot 应用程序中,应用程序模块是一个功能单元,包含以下部分:

  • 由 Spring bean 实例实现并由模块发布的应用程序事件公开给其他模块的 API,通常称为 provided interface

  • 其他模块不应该访问的内部实现组件。

  • 以 Spring bean 依赖项、应用程序事件侦听和公开的配置属性的形式引用其他模块公开的 API,通常称为 required interface

Spring Modulith 提供了在 Spring Boot 应用程序中表达模块的不同方法,主要区别在于总体安排中所涉及的复杂程度。这允许开发人员从简单开始,并在需要时自然地转向更复杂的手段。

Simple Application Modules

应用程序的 主程序包 是包含主应用程序类的程序包,即使用 @SpringBootApplication 进行注释的类,它通常包含用于运行该类的 main(…) 方法。默认情况下,主包的每个直接子包都被视为 应用程序模块包

如果此包不包含任何子包,它就会被视作一个简单的包。它允许使用 Java 的包范围将其内部的代码隐藏起来,以防止其他包中的代码引用这些类型,从而不让这些类型受到这些包的依赖注入的影响。因此,自然而然地,模块的 API 包括包中的所有公开类型。

让我们看一下一个示例安排 ([role="green"] 表示公有类型,[role="red"] 表示包私有类型)。

A single inventory application module
 Example
└─  src/main/java
   ├─  example                        1
   |  └─ [role="green"] Application.java
   └─  example.inventory              2
      ├─ [role="green"] InventoryManagement.java
      └─ [role="red"] SomethingInventoryInternal.java
1 应用程序的主包 example
2 一个应用程序模块包 inventory

Advanced Application Modules

如果应用程序模块包包含子包,则可能需要公开这些包中的类型,以便可以从同一模块的代码引用它。

An inventory and order application module
 Example
└─  src/main/java
   ├─  example
   |  └─ [role="green"] Application.java
   ├─  example.inventory
   |  ├─ [role="green"] InventoryManagement.java
   |  └─ [role="red"] SomethingInventoryInternal.java
   ├─ * example.order*
   |  └─ [role="green"] OrderManagement.java
   └─  example.order.internal
      └─ [role="green"] SomethingOrderInternal.java

在这种排列中,order 包被视为 API 包。允许来自其他应用程序模块的代码引用其中的类型。order.internal 就像应用程序模块基础包的任何其他子包一样,被视为 内部 包。这些包中的代码不得从其他模块引用。请注意,SomethingOrderInternal 如何成为一个公开类型,这可能是因为 OrderManagement 依赖于它。不幸的是,这意味着也可以从其他包(如 inventory 包)引用它。在这种情况下,Java 编译器在防止这些非法引用方面并没有多大用处。

Open Application Modules

因为 above 中描述的安排仅向其他模块公开主动选择公开的类型,所以可视为封闭的。当将 Spring Modulith 应用于遗留应用程序时,将位于嵌套包中的所有类型都隐藏起来可能不足,或者可能需要将所有这些包也标记为要公开的包。

要将应用程序模块转换为开放模块,请在 package-info.java 类型上使用 @ApplicationModule 注解。

Declaring an Application Modules as Open
  • Java

  • Kotlin

将应用程序模块声明为开放后,将对验证进行以下更改:

  • 通常允许从其他模块访问应用程序模块内部类型。

  • 所有类型,也包括基础应用程序模块包的子包中驻留的类型,都会被添加到 unnamed named interface,除非明确分配给某个命名接口。

此功能主要用于现有项目的代码库,它们逐渐迁移到 Spring Modulith 建议的打包结构。在完全模块化的应用程序中,使用开放应用程序模块通常提示模块化和打包结构达不到最优。

Explicit Application Module Dependencies

模块可以通过包上的 @ApplicationModule 注解选择声明其允许的依赖关系,该注解通过 package-info.java 文件表示。例如,Kotlin 缺乏对该文件提供支持,因此您还可以在位于应用程序模块根包中的单个类型上使用该注解。

Inventory explicitly configuring module dependencies
  • Java

  • Kotlin

import org.springframework.modulith.ApplicationModule

@ApplicationModule(allowedDependencies = "order")
class ModuleMetadata {}

在这种情况下,inventory 模块中的代码只能引用 order 模块中的代码(以及首先未分配到任何模块中的代码)。了解如何在 Verifying Application Module Structure 中监控该代码。

The ApplicationModules Type

Spring Moduliths 允许检查代码库,以基于给定的排列和可选的配置派生出一个应用程序模块模型。spring-modulith-core 工件包含可以指向 Spring Boot 应用程序类的 ApplicationModules

Creating an application module model
  • Java

  • Kotlin

var modules = ApplicationModules.of(Application.class);
var modules = ApplicationModules.of(Application::class)

为了了解已分析的排列是什么样子的,我们可以只将包含在整个模型中的各个模块写到控制台中:

Writing the application module arrangement to the console
  • Java

  • Kotlin

modules.forEach(System.out::println);
modules.forEach(println(it))
The console output of our application module arrangement
## example.inventory ##
> Logical name: inventory
> Base package: example.inventory
> Spring beans:
  + ….InventoryManagement
  o ….SomeInternalComponent

## example.order ##
> Logical name: order
> Base package: example.order
> Spring beans:
  + ….OrderManagement
  + ….internal.SomeInternalComponent

请注意,列出了每个模块,识别出了包含的 Spring 组件,并且也渲染了各自的可见性。

Named Interfaces

根据 Advanced Application Modules 中的描述,默认情况下,应用程序模块的基本包被视为 API 包,因此是允许来自其他模块的传入依赖项的唯一包。如果你想向其他模块公开其他包,则需要使用 named interfaces。你可以通过使用 @NamedInterface 或明确标注了 @org.springframework.modulith.PackageInfo 的类型来注释这些包的 package-info.java 文件来实现此目的。

A package arrangement to encapsulate an SPI named interface
 Example
└─  src/main/java
   ├─  example
   |  └─ [role="green"] Application.java
   ├─ …
   ├─  example.order
   |  └─ [role="green"] OrderManagement.java
   ├─ * example.order.spi*
   |  ├—  package-info.java
   |  └─ [role="green"] SomeSpiInterface.java
   └─  example.order.internal
      └─ [role="green"] SomethingOrderInternal.java
package-info.java in example.order.spi
  • Java

  • Kotlin

import org.springframework.modulith.PackageInfo
import org.springframework.modulith.NamedInterface

@PackageInfo
@NamedInterface("spi")
class ModuleMetadata {}

该声明的效果是双重的:首先,允许其他应用程序模块中的代码引用 SomeSpiInterface。应用程序模块能够在明确的依赖关系声明中引用命名接口。假设 inventory 模块正在使用它,则它可以像这样引用上面声明的命名接口:

  • Java

  • Kotlin

请注意,我们将命名接口的名字 spi 通过双冒号 :: 连接起来。在此设置中,inventory 中的代码将被允许依赖 SomeSpiInterface 和驻留在 order.spi 接口中的其他代码,但不能依赖例如 OrderManagement。对于没有明确描述依赖关系的模块,应用程序模块根包 SPI 包都可以访问。

Customizing Module Detection

如果默认的应用程序模块模型不适用于您的应用程序,可以通过提供 ApplicationModuleDetectionStrategy 的实现来自定义模块的检测。该接口公开了一个单一的方法 Stream<JavaPackage> getModuleBasePackages(JavaPackage),并且它将使用 Spring Boot 应用程序类所在的包被调用。然后,您可以检查驻留在该包内的包,并根据命名约定或类似约定选择要视为应用程序模块基础包的包。

假设您声明了一个自定义的 ApplicationModuleDetectionStrategy 实现如下:

  • Java

  • Kotlin

class CustomApplicationModuleDetectionStrategy implements ApplicationModuleDetectionStrategy {

  @Override
  public Stream<JavaPackage> getModuleBasePackages(JavaPackage basePackage) {
    // Your module detection goes here
  }
}
class CustomApplicationModuleDetectionStrategy : ApplicationModuleDetectionStrategy {

  override fun getModuleBasePackages(basePackage: JavaPackage): Stream<JavaPackage> {
    // Your module detection goes here
  }
}

此类需要在 META-INF/spring.factories 中注册,如下所示:

org.springframework.modulith.core.ApplicationModuleDetectionStrategy=\
  example.CustomApplicationModuleDetectionStrategy

Customizing the Application Modules Arrangement

Spring Moduliths 允许通过要在 Spring Boot 应用程序主类上使用的 @Modulithic 注解配置围绕您通过应用程序模块排列创建的某些核心方面。

  • Java

  • Kotlin

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.modulith.Modulithic;

@Modulithic
@SpringBootApplication
class MyApplication {

  public static void main(String... args) {
    SpringApplication.run(DemoApplication.class, args);
  }
}
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.modulith.Modulithic

@Modulithic
@SpringBootApplication
class DemoApplication

fun main(args: Array<String>) {
  runApplication<DemoApplication>(*args)
}

注释展示了用于定制的以下属性:

Annotation attribute Description

systemName

用于生成的 documentation中应用程序的人类可读名称。

sharedModules

将命名应用模块声明为共享模块,这意味着它们将始终包含在 application module integration tests中。

additionalPackages

指示 Spring Modulith 将配置的程序包视为其他根应用程序程序包。换句话说,也会触发这些程序包的应用程序模块检测。