基础
Spring Modulith 支持开发人员在 Spring Boot 应用程序中实现逻辑模块。它允许他们应用结构验证,记录模块排列,运行各个模块的集成测试,观察模块在运行时的交互,并以松散耦合的方式实现模块交互。本节将讨论开发人员在深入了解技术支持之前需要理解的基本概念。
应用程序模块
在 Spring Boot 应用程序中,应用程序模块是一个由以下部分组成的功能单元
-
由 Spring bean 实例和模块发布的应用程序事件实现并公开给其他模块的 API,通常称为提供的接口。
-
内部实现组件,其他模块不应访问这些组件。
-
以 Spring bean 依赖项、侦听的应用程序事件和公开的配置属性的形式引用其他模块公开的 API,通常称为必需接口。
Spring Modulith 提供了在 Spring Boot 应用程序中表示模块的不同方法,主要区别在于整体排列中涉及的复杂性级别。这允许开发人员从简单开始,并在需要时自然地转向更复杂的方法。
ApplicationModules
类型
Spring Modulith 允许检查代码库以根据给定的排列和可选配置导出应用程序模块模型。spring-modulith-core
工件包含可以指向 Spring Boot 应用程序类的 ApplicationModules
-
Java
-
Kotlin
var modules = ApplicationModules.of(Application.class);
var modules = ApplicationModules.of(Application::class)
modules
将包含从代码库派生的应用程序模块排列的内存表示。该部分的哪些部分将被检测为模块取决于类指向的包所在的 Java 包结构。在 简单应用程序模块 中了解有关默认情况下预期的排列的更多信息。高级排列和自定义选项在 高级应用程序模块 中进行了描述,并且
为了对分析的排列的外观有一个印象,我们只需将包含在整体模型中的各个模块写入控制台即可
-
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 组件,并呈现各自的可见性。
排除包
如果您想从应用程序模块检查中排除某些 Java 类或完整包,可以使用
-
Java
-
Kotlin
ApplicationModules.of(Application.class, JavaClass.Predicates.resideInAPackage("com.example.db")).verify();
ApplicationModules.of(Application::class, JavaClass.Predicates.resideInAPackage("com.example.db")).verify()
其他排除示例
-
com.example.db
— 匹配给定包com.example.db
中的所有文件。 -
com.example.db..
— 匹配给定包(com.example.db
)和所有子包(com.example.db.a
或com.example.db.b.c
)中的所有文件。 -
..example..
— 匹配a.example
、a.example.b
或a.b.example.c.d
,但不匹配a.exam.b
可以在 ArchUnit PackageMatcher
的 JavaDoc 中找到有关可能的匹配器的完整详细信息。
简单应用程序模块
应用程序的主包是主应用程序类所在的包。该类用 @SpringBootApplication
注释,并且通常包含用于运行它的 main(…)
方法。默认情况下,主包的每个直接子包都被视为应用程序模块包。
如果此包不包含任何子包,则认为它是一个简单的包。它允许使用 Java 的包范围隐藏其中的代码,以隐藏类型不被驻留在其他包中的代码引用,因此不受对这些包的依赖注入的影响。因此,模块的 API 自然由包中的所有公共类型组成。
让我们看一个示例排列( 表示公共类型, 表示包私有类型)。
Example
└─ src/main/java
├─ example (1)
| └─ Application.java
└─ example.inventory (2)
├─ InventoryManagement.java
└─ SomethingInventoryInternal.java
1 | 应用程序的主包 example 。 |
2 | 应用程序模块包 inventory 。 |
高级应用程序模块
如果应用程序模块包包含子包,则可能需要将这些子包中的类型设为公共,以便可以从同一模块的代码中引用它。
Example
└─ src/main/java
├─ example
| └─ Application.java
├─ example.inventory
| ├─ InventoryManagement.java
| └─ SomethingInventoryInternal.java
├─ example.order
| └─ OrderManagement.java
└─ example.order.internal
└─ SomethingOrderInternal.java
在这种情况下,order
包被视为 API 包。允许其他应用程序模块中的代码引用其中的类型。order.internal
,就像应用程序模块基本包的任何其他子包一样,被视为内部包。不能从其他模块引用其中的代码。请注意,SomethingOrderInternal
是一个公共类型,可能是因为 OrderManagement
依赖于它。不幸的是,这意味着也可以从其他包(例如 inventory
包)引用它。在这种情况下,Java 编译器在防止这些非法引用方面作用不大。
开放应用程序模块
上面描述的安排被视为封闭的,因为它们只向其他模块公开主动选择公开的类型。在将 Spring Modulith 应用于旧应用程序时,隐藏嵌套包中位于其他模块的所有类型可能不充分,或者需要标记所有这些包以进行公开。
要将应用程序模块转换为开放模块,请在 package-info.java
类型上使用 @ApplicationModule
注解。
-
Java
-
Kotlin
@org.springframework.modulith.ApplicationModule(
type = Type.OPEN
)
package example.inventory;
@org.springframework.modulith.ApplicationModule(
type = Type.OPEN
)
package example.inventory
将应用程序模块声明为开放将对验证造成以下更改
-
通常允许从其他模块访问应用程序模块内部类型。
-
所有类型(也包括驻留在应用程序模块基本包的子包中的类型)都将添加到未命名命名接口中,除非明确分配给命名接口。
此功能主要用于现有项目的代码库,这些代码库逐渐迁移到 Spring Modulith 推荐的打包结构。在完全模块化的应用程序中,使用开放应用程序模块通常暗示模块化和打包结构不理想。 |
显式应用程序模块依赖项
模块可以选择通过在包上使用 @ApplicationModule
注解来声明其允许的依赖项,该注解通过 package-info.java
文件表示。例如,由于 Kotlin 缺少对该文件支持,你也可以在位于应用程序模块根包中的单个类型上使用该注解。
-
Java
-
Kotlin
@org.springframework.modulith.ApplicationModule(
allowedDependencies = "order"
)
package example.inventory;
package example.inventory
import org.springframework.modulith.ApplicationModule
@ApplicationModule(allowedDependencies = "order")
class ModuleMetadata {}
在这种情况下,inventory 模块中的代码只允许引用 order 模块中的代码(以及最初未分配给任何模块的代码)。了解如何在验证应用程序模块结构中监视它。
命名接口
默认情况下,正如高级应用程序模块中所述,应用程序模块的基本包被视为 API 包,因此是唯一允许来自其他模块的传入依赖项的包。如果你希望向其他模块公开其他包,则需要使用命名接口。你可以通过使用 @NamedInterface
或明确用 @org.springframework.modulith.PackageInfo
注解的类型来注释这些包的 package-info.java
文件来实现这一点。
Example
└─ src/main/java
├─ example
| └─ Application.java
├─ …
├─ example.order
| └─ OrderManagement.java
├─ example.order.spi
| ├— package-info.java
| └─ SomeSpiInterface.java
└─ example.order.internal
└─ SomethingOrderInternal.java
example.order.spi
中的 package-info.java
-
Java
-
Kotlin
@org.springframework.modulith.NamedInterface("spi")
package example.order.spi;
package example.order.spi
import org.springframework.modulith.PackageInfo
import org.springframework.modulith.NamedInterface
@PackageInfo
@NamedInterface("spi")
class ModuleMetadata {}
该声明的效果是双重的:首先,其他应用程序模块中的代码可以引用 SomeSpiInterface
。应用程序模块能够在显式依赖声明中引用命名接口。假设 inventory 模块正在使用它,它可以像这样引用上面声明的命名接口
-
Java
-
Kotlin
@org.springframework.modulith.ApplicationModule(
allowedDependencies = "order :: spi"
)
package example.inventory;
@org.springframework.modulith.ApplicationModule(
allowedDependencies = "order :: spi"
)
package example.inventory
请注意,我们如何通过双冒号 ::
连接命名接口的名称 spi
。在此设置中,inventory 中的代码将被允许依赖于 SomeSpiInterface
和驻留在 order.spi
接口中的其他代码,但不能依赖于 OrderManagement
。对于没有明确描述依赖项的模块,应用程序模块根包和SPI 包都是可访问的。
如果您想表示应用程序模块可以引用所有显式声明的命名接口,则可以使用星号 (*
) 如下所示
-
Java
-
Kotlin
@org.springframework.modulith.ApplicationModule(
allowedDependencies = "order :: *"
)
package example.inventory;
@org.springframework.modulith.ApplicationModule(
allowedDependencies = "order :: *"
)
package example.inventory
自定义模块检测
如果默认应用程序模块模型不适用于您的应用程序,则可以通过提供 ApplicationModuleDetectionStrategy
的实现来自定义模块的检测。该接口公开了一个单一方法 Stream<JavaPackage> getModuleBasePackages(JavaPackage)
,并将使用 Spring Boot 应用程序类所在的包调用它。然后,您可以检查驻留在其中的包,并根据命名约定等选择要作为应用程序模块基本包的包。
假设您声明了一个自定义 ApplicationModuleDetectionStrategy
实现,如下所示
-
Java
-
Kotlin
package example;
class CustomApplicationModuleDetectionStrategy implements ApplicationModuleDetectionStrategy {
@Override
public Stream<JavaPackage> getModuleBasePackages(JavaPackage basePackage) {
// Your module detection goes here
}
}
package example
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
自定义应用程序模块安排
Spring Moduliths 允许通过 @Modulithic
注释(在主 Spring Boot 应用程序类上使用)配置围绕您通过该注释创建的应用程序模块安排的一些核心方面。
-
Java
-
Kotlin
package example;
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);
}
}
package example
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)
}
该注释公开以下属性以进行自定义
注释属性 | 描述 |
---|---|
|
要在生成的文档中使用的应用程序的可读名称。 |
|
将具有给定名称的应用程序模块声明为共享模块,这意味着它们将始终包含在应用程序模块集成测试中。 |
|
指示 Spring Modulith 将配置的包视为其他根应用程序包。换句话说,应用程序模块检测也将为此触发。 |