基础
Spring Modulith 支持开发人员在 Spring Boot 应用程序中实现逻辑模块。 它允许他们应用结构验证、记录模块安排、为单个模块运行集成测试、在运行时观察模块的交互,并且通常以松散耦合的方式实现模块交互。 本节将讨论开发人员在深入研究技术支持之前需要了解的基本概念。
应用程序模块
在 Spring Boot 应用程序中,应用程序模块是一个功能单元,它由以下部分组成:
-
向 Spring Bean 实例实现的其他模块和模块发布的应用程序事件公开的 API,通常称为提供的接口。
-
不应被其他模块访问的内部实现组件。
-
对其他模块以 Spring bean 依赖项、侦听的应用程序事件和公开的配置属性的形式公开的 API 的引用,通常称为必需接口。
Spring Moduliths 提供了在 Spring Boot 应用程序中表达模块的不同方式,主要区别在于整体安排所涉及的复杂程度。 这允许开发人员从简单开始,然后根据需要自然地转向更复杂的方法。
类型ApplicationModules
Spring Moduliths 允许检查代码库,以根据给定的 arrangement 和可选配置派生应用程序模块模型。
工件包含可以指向 Spring Boot 应用程序类的 :spring-modulith-core
ApplicationModules
-
Java
-
Kotlin
var modules = ApplicationModules.of(Application.class);
val modules = ApplicationModules.of(Application::class.java)
它将包含从代码库派生的应用程序模块排列的内存中表示。
其中的哪些部分将被检测为模块取决于类指向的包所在的包下面的 Java 包结构。
了解有关 Simple Application Modules 中默认预期的排列方式的更多信息。
高级排列和定制选项在高级应用程序模块和modules
为了获得所分析的 arrangement 的印象,我们只需将整个模型中包含的各个模块写入控制台:
-
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.java, 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 中找到。
简单的应用程序模块
应用程序的主包是主应用程序类所在的包。
即 class ,它带有 Comments ,通常包含用于运行它的方法。
默认情况下,主包的每个直接子包都被视为一个应用程序模块包。@SpringBootApplication
main(…)
如果此 package 不包含任何子 package,则认为它是一个简单的 package。 它允许通过使用 Java 的包范围来隐藏其中的代码,以隐藏驻留在其他包中的代码引用的类型,从而不受依赖项注入到这些包中的影响。 因此,该模块的 API 自然由包中的所有公共类型组成。
让我们看一个示例安排(表示 public 类型, package-private 类型)。
Example
└─ src/main/java
├─ example (1)
| └─ Application.java
└─ example.inventory (2)
├─ InventoryManagement.java
└─ SomethingInventoryInternal.java
1 | 应用程序的 main package .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
在这种安排中,该包被视为 API 包。
允许来自其他应用程序模块的代码引用其中的类型。,就像应用程序模块基础包的任何其他子包一样,被视为内部子包。
这些模块中的代码不得从其他模块引用。
请注意 public 类型如何,可能是因为依赖于它。
不幸的是,这意味着它也可以从其他软件包(例如 one)中引用。
在这种情况下,Java 编译器对于防止这些非法引用没有多大用处。order
order.internal
SomethingOrderInternal
OrderManagement
inventory
嵌套应用程序模块
从版本 1.3 开始,Spring Modulith 应用程序模块可以包含嵌套模块。
这允许在模块包含要依次逻辑分离的部件的情况下管理内部结构。
要定义嵌套的应用程序模块,请显式注释应该构成的包。@ApplicationModule
Example
└─ src/main/java
|
├─ example
| └─ Application.java
|
| -> Inventory
|
├─ example.inventory
| ├─ InventoryManagement.java
| └─ SomethingInventoryInternal.java
├─ example.inventory.internal
| └─ SomethingInventoryInternal.java
|
| -> Inventory > Nested
|
├─ example.inventory.nested
| ├─ package-info.java // @ApplicationModule
| └─ NestedApi.java
├─ example.inventory.nested.internal
| └─ NestedInternal.java
|
| -> Order
|
└─ example.order
├─ OrderManagement.java
└─ SomethingOrderInternal.java
在此示例中,是如上所述的应用程序模块。
包上的注释导致它反过来成为嵌套的应用程序模块。
在这种安排中,以下访问规则适用:inventory
@ApplicationModule
nested
-
Nested 中的代码只能从 Inventory 或嵌套在 Inventory 中的同级应用程序模块公开的任何类型的代码中获得。
-
Nested 模块中的任何代码都可以访问父模块中的代码,甚至是内部模块中的代码。 即,两者 和 都可以访问 .
NestedApi
NestedInternal
inventory.internal.SomethingInventoryInternal
-
嵌套模块中的代码还可以访问顶级应用程序模块的公开类型。 中的任何代码(或任何子包)都可以访问 .
nested
OrderManagement
打开应用程序模块
上述安排被认为是封闭的,因为它们只将类型公开给主动选择公开的其他模块。 当将 Spring Modulith 应用于遗留应用程序时,对其他模块隐藏位于嵌套包中的所有类型可能是不够的,或者还需要标记所有这些包以进行公开。
要将应用程序模块转换为开放模块,请在 type 上使用 Comments。@ApplicationModule
package-info.java
-
Java
-
Kotlin
@org.springframework.modulith.ApplicationModule(
type = Type.OPEN
)
package example.inventory;
@org.springframework.modulith.ApplicationModule(
type = Type.OPEN
)
package example.inventory
将应用程序模块声明为 open 将导致验证发生以下更改:
-
通常允许从其他模块访问 application module 内部类型。
-
所有类型(包括驻留在应用程序模块基础包的子包中的类型)都将添加到未命名的命名接口中,除非显式分配给命名接口。
此功能主要用于现有项目的代码库,这些项目正在逐渐转向 Spring Modulith 推荐的打包结构。 在完全模块化的应用程序中,使用开放式应用程序模块通常暗示着次优的模块化和打包结构。 |
显式应用程序模块依赖项
模块可以通过在包上使用 Annotation 来选择声明其允许的依赖项,该 Comments 由文件表示。
例如,由于 Kotlin 不支持该文件,因此您还可以对位于应用程序模块的根包中的单个类型使用注释。@ApplicationModule
package-info.java
-
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 包,因此是唯一允许从其他模块传入依赖项的包。
如果您想将其他软件包公开给其他模块,则需要使用命名接口。
您可以通过使用这些包的文件或显式注释的类型来实现这一点。package-info.java
@NamedInterface
@org.springframework.modulith.PackageInfo
Example
└─ src/main/java
├─ example
| └─ Application.java
├─ …
├─ example.order
| └─ OrderManagement.java
├─ example.order.spi
| ├— package-info.java
| └─ SomeSpiInterface.java
└─ example.order.internal
└─ SomethingOrderInternal.java
package-info.java
在example.order.spi
-
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 {}
该声明的效果是双重的:首先,允许其他应用程序模块中的代码引用 。
应用程序模块能够在显式依赖项声明中引用命名接口。
假设 inventory 模块正在使用它,它可以引用上面声明的命名接口,如下所示:SomeSpiInterface
-
Java
-
Kotlin
@org.springframework.modulith.ApplicationModule(
allowedDependencies = "order :: spi"
)
package example.inventory;
@org.springframework.modulith.ApplicationModule(
allowedDependencies = "order :: spi"
)
package example.inventory
请注意我们如何通过双冒号连接命名接口的名称。
在此设置中,允许 inventory 中的代码依赖于 interface 中的其他代码,但不允许 on。
对于没有明确描述依赖关系的模块,应用程序模块根包和 SPI 根包都可以访问。spi
::
SomeSpiInterface
order.spi
OrderManagement
如果要表示允许应用程序模块引用所有显式声明的命名接口,则可以使用星号 (),如下所示:*
-
Java
-
Kotlin
@org.springframework.modulith.ApplicationModule(
allowedDependencies = "order :: *"
)
package example.inventory;
@org.springframework.modulith.ApplicationModule(
allowedDependencies = "order :: *"
)
package example.inventory
自定义应用程序模块排列
Spring Moduliths 允许围绕您通过 Comments 创建的应用程序模块安排配置一些核心方面,以便在主 Spring Boot 应用程序类上使用。@Modulithic
-
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(MyApplication.class, args);
}
}
package example
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.modulith.Modulithic
@Modulithic
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
注释公开了以下要自定义的属性:
Annotation 属性 | 描述 |
---|---|
|
要在生成的文档中使用的应用程序的人类可读名称。 |
|
将具有给定名称的应用程序模块声明为共享模块,这意味着它们将始终包含在应用程序模块集成测试中。 |
|
指示 Spring Modulith 将配置的包视为额外的根应用程序包。换句话说,也会为这些触发应用程序模块检测。 |
自定义模块检测
默认情况下,应用程序模块将位于 Spring Boot 应用程序类所在的包的直接子包中。
可以激活另一种检测策略,以仅考虑通过 Spring Modulith 或 jMolecules 注释显式注释的包。
可以通过将 配置为 来激活该策略。@ApplicationModule
@Module
spring.modulith.detection-strategy
explicitly-annotated
spring.modulith.detection-strategy=explicitly-annotated
如果默认应用程序模块检测策略和手动注释的检测策略都不适用于您的应用程序,则可以通过提供 .
该接口公开了一个方法,并将使用 Spring Boot 应用程序类所在的包调用。
然后,您可以检查驻留在其中的包,并根据命名约定等选择要被视为应用程序模块基础包的包。ApplicationModuleDetectionStrategy
Stream<JavaPackage> getModuleBasePackages(JavaPackage)
假设您声明了一个自定义实现,如下所示:ApplicationModuleDetectionStrategy
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
}
}
现在可以按如下方式注册此类:spring.modulith.detection-strategy
spring.modulith.detection-strategy=example.CustomApplicationModuleDetectionStrategy
从其他软件包贡献应用程序模块
虽然允许定义触发 application module detection 的包,而不是 an-comments 类之一,但它的使用需要提前了解这些包。
从版本 1.3 开始,Spring Modulith 支持通过 和 abstractions 对应用程序模块的外部贡献。
后者的实现可以在位于 中的文件中注册。@Modulithic
additionalPackages
ApplicationModuleSource
ApplicationModuleSourceFactory
spring.factories
META-INF
org.springframework.modulith.core.ApplicationModuleSourceFactory=example.CustomApplicationModuleSourceFactory
这样的工厂可以返回任意包名称来获取应用,也可以显式返回包来为其创建模块。ApplicationModuleDetectionStrategy
package example;
public class CustomApplicationModuleSourceFactory implements ApplicationModuleSourceFactory {
@Override
public List<String> getRootPackages() {
return List.of("com.acme.toscan");
}
@Override
public ApplicationModuleDetectionStrategy getApplicationModuleDetectionStrategy() {
return ApplicationModuleDetectionStrategy.explicitlyAnnotated();
}
@Override
public List<String> getModuleBasePackages() {
return List.of("com.acme.module");
}
}
上面的示例将用于检测其中显式声明的模块,并从 创建应用程序模块 。
从这些返回的包名称随后将通过 中公开的相应风格转换为 s 。com.acme.toscan
com.acme.module
ApplicationModuleSource
getApplicationModuleSource(…)
ApplicationModuleDetectionStrategy