对于最新的稳定版本,请使用 Spring Modulith 1.4.3! |
基础
Spring Modulith 支持开发人员在 Spring Boot 应用程序中实现逻辑模块。 它允许他们应用结构验证、记录模块排列、对单个模块运行集成测试、在运行时观察模块的交互,并通常以松散耦合的方式实现模块交互。 本节将讨论开发人员在深入了解技术支持之前需要了解的基本概念。
应用模块
在 Spring Boot 应用程序中,应用程序模块是一个功能单元,由以下部分组成:
-
向 Spring bean 实例实现的其他模块和模块发布的应用程序事件公开的 API,通常称为提供的接口。
-
不应由其他模块访问的内部实现组件。
-
其他模块以 Spring bean 依赖项、监听的应用程序事件和公开的配置属性的形式公开的 API 的引用,通常称为必需接口。
Spring Moduliths 提供了在 Spring Boot 应用程序中表达模块的不同方式,主要区别在于整体排列中涉及的复杂程度。 这允许开发人员从简单的开始,并在需要时自然地转向更复杂的方法。
这ApplicationModules
类型
Spring Moduliths 允许检查代码库,以根据给定的排列和可选配置派生应用程序模块模型。
这spring-modulith-core
工件包含ApplicationModules
可以指向 Spring Boot 应用程序类:
-
Java
-
Kotlin
var modules = ApplicationModules.of(Application.class);
val modules = ApplicationModules.of(Application::class.java)
这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.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 的 JavaDoc 中找到PackageMatcher
.
简单的应用模块
应用程序的主包是主应用程序类所在的包。
这就是类,用@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
package 被视为 API 包。
允许来自其他应用程序模块的代码引用其中的类型。order.internal
,就像应用程序模块基本包的任何其他子包一样,被视为内部子包。
其中的代码不得从其他模块引用。
请注意如何SomethingOrderInternal
是公共类型,可能是因为OrderManagement
取决于它。
不幸的是,这意味着它也可以从其他包中引用,例如inventory
一。
在这种情况下,Java 编译器对于防止这些非法引用没有多大用处。
开放应用模块
上述安排被认为是封闭的,因为它们仅将类型暴露给主动选择进行公开的其他模块。 将 Spring Modulith 应用于遗留应用程序时,从其他模块中隐藏位于嵌套包中的所有类型可能是不够的,或者也需要标记所有这些包以进行暴露。
要将应用程序模块转换为开放模块,请使用@ApplicationModule
注释package-info.java
类型。
-
Java
-
Kotlin
@org.springframework.modulith.ApplicationModule(
type = Type.OPEN
)
package example.inventory;
package example.inventory
import org.springframework.modulith.ApplicationModule
import org.springframework.modulith.PackageInfo
@ApplicationModule(
type = Type.OPEN
)
@PackageInfo
class ModuleMetadata {}
将应用程序模块声明为打开将导致验证发生以下更改:
-
通常允许从其他模块访问应用程序模块内部类型。
-
所有类型,以及驻留在应用程序模块基本包的子包中的类型,都将添加到未命名的命名接口中,除非显式分配给命名接口。
此功能旨在主要用于逐渐迁移到 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 {}
在这种情况下,库存模块中的代码只允许引用订单模块中的代码(并且代码首先未分配给任何模块)。 在验证应用程序模块结构中了解如何监视它。
命名接口
默认情况下,如高级应用程序模块中所述,应用程序模块的基本包被视为 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 {}
该声明的效果是双重的:首先,允许其他应用程序模块中的代码引用SomeSpiInterface
. 应用程序模块能够在显式依赖声明中引用命名接口。假设清单模块正在使用它,它可以引用上面声明的命名接口,如下所示:
-
Java
-
Kotlin
@org.springframework.modulith.ApplicationModule(
allowedDependencies = "order :: spi"
)
package example.inventory;
package example.inventory
import org.springframework.modulith.ApplicationModule
import org.springframework.modulith.PackageInfo
@ApplicationModule(
allowedDependencies = "order :: spi"
)
@PackageInfo
class ModuleMetadata {}
请注意我们如何连接命名接口的名称spi
通过双冒号::
.
在此设置中,允许清单中的代码依赖于SomeSpiInterface
以及驻留在order.spi
接口,但未打开OrderManagement
例如。
对于没有明确描述依赖关系的模块,应用程序模块根包和 SPI 包都可以访问。
如果要表示允许应用程序模块引用所有显式声明的命名接口,则可以使用星号 () 如下所示:*
-
Java
-
Kotlin
@org.springframework.modulith.ApplicationModule(
allowedDependencies = "order :: *"
)
package example.inventory;
package example.inventory
import org.springframework.modulith.ApplicationModule
import org.springframework.modulith.PackageInfo
@ApplicationModule(
allowedDependencies = "order :: *"
)
@PackageInfo
class ModuleMetadata {}
自定义模块检测
默认情况下,应用程序模块应位于 Spring Boot 应用程序类所在的包的直接子包中。
可以激活替代检测策略,以仅考虑显式注释的包,可以通过 Spring Modulis 的@ApplicationModule
或 jMolecules@Module
注解。
可以通过配置spring.modulith.detection-strategy
自explicitly-annotated
.
spring.modulith.detection-strategy=explicitly-annotated
如果默认的应用程序模块检测策略和手动注释的检测策略都不适用于您的应用程序,则可以通过提供ApplicationModuleDetectionStrategy
.
该接口公开单个方法Stream<JavaPackage> getModuleBasePackages(JavaPackage)
,并将与 Spring Boot 应用程序类所在的包一起调用。
然后,您可以检查驻留在其中的包,并根据命名约定等选择要被视为应用程序模块基本包的包。
假设您声明了自定义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
自定义应用程序模块安排
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(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)
}
注释公开了以下要自定义的属性:
注释属性 | 描述 |
---|---|
|
要在生成的文档中使用的应用程序的人类可读名称。 |
|
将具有给定名称的应用程序模块声明为共享模块,这意味着它们将始终包含在应用程序模块集成测试中。 |
|
指示 Spring Modulith 将配置的包视为额外的根应用程序包。换句话说,应用程序模块检测也将针对这些程序触发。 |