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"] 表示包私有类型)。
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
如果应用程序模块包包含子包,则可能需要公开这些包中的类型,以便可以从同一模块的代码引用它。
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
注解。
-
Java
-
Kotlin
将应用程序模块声明为开放后,将对验证进行以下更改:
-
通常允许从其他模块访问应用程序模块内部类型。
-
所有类型,也包括基础应用程序模块包的子包中驻留的类型,都会被添加到 unnamed named interface,除非明确分配给某个命名接口。
此功能主要用于现有项目的代码库,它们逐渐迁移到 Spring Modulith 建议的打包结构。在完全模块化的应用程序中,使用开放应用程序模块通常提示模块化和打包结构达不到最优。 |
Explicit Application Module Dependencies
模块可以通过包上的 @ApplicationModule
注解选择声明其允许的依赖关系,该注解通过 package-info.java
文件表示。例如,Kotlin 缺乏对该文件提供支持,因此您还可以在位于应用程序模块根包中的单个类型上使用该注解。
-
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
:
-
Java
-
Kotlin
var modules = ApplicationModules.of(Application.class);
var modules = ApplicationModules.of(Application::class)
为了了解已分析的排列是什么样子的,我们可以只将包含在整个模型中的各个模块写到控制台中:
-
Java
-
Kotlin
modules.forEach(System.out::println);
modules.forEach(println(it))
## 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
文件来实现此目的。
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 |
---|---|
|
用于生成的 documentation中应用程序的人类可读名称。 |
|
将命名应用模块声明为共享模块,这意味着它们将始终包含在 application module integration tests中。 |
|
指示 Spring Modulith 将配置的程序包视为其他根应用程序程序包。换句话说,也会触发这些程序包的应用程序模块检测。 |