此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Modulith 1.3.0spring-doc.cadn.net.cn

基础

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

应用程序模块

在 Spring Boot 应用程序中,应用程序模块是一个功能单元,它由以下部分组成:spring-doc.cadn.net.cn

  • 向 Spring Bean 实例实现的其他模块和模块发布的应用程序事件公开的 API,通常称为提供的接口spring-doc.cadn.net.cn

  • 不应被其他模块访问的内部实现组件。spring-doc.cadn.net.cn

  • 对其他模块以 Spring bean 依赖项、侦听的应用程序事件和公开的配置属性的形式公开的 API 的引用,通常称为必需接口spring-doc.cadn.net.cn

Spring Moduliths 提供了在 Spring Boot 应用程序中表达模块的不同方式,主要区别在于整体安排所涉及的复杂程度。 这允许开发人员从简单开始,然后根据需要自然地转向更复杂的方法。spring-doc.cadn.net.cn

ApplicationModules类型

Spring Moduliths 允许检查代码库,以根据给定的 arrangement 和可选配置派生应用程序模块模型。 这spring-modulith-coreartifact 包含ApplicationModules可以指向 Spring Boot 应用程序类:spring-doc.cadn.net.cn

创建应用程序模块模型
var modules = ApplicationModules.of(Application.class);
val modules = ApplicationModules.of(Application::class.java)

modules将包含从代码库派生的应用程序模块排列的内存中表示。 其中的哪些部分将被检测为模块取决于类指向的包所在的包下面的 Java 包结构。 了解有关 Simple Application Modules 中默认预期的排列方式的更多信息。 高级排列和定制选项在高级应用程序模块spring-doc.cadn.net.cn

为了获得所分析的 arrangement 的印象,我们只需将整个模型中包含的各个模块写入控制台:spring-doc.cadn.net.cn

将应用程序模块排列写入控制台
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 组件是如何被识别的,以及相应的可见性是如何呈现的。spring-doc.cadn.net.cn

排除包

如果您想从应用程序模块检查中排除某些 Java 类或完整包,您可以通过以下方式执行此作:spring-doc.cadn.net.cn

ApplicationModules.of(Application.class, JavaClass.Predicates.resideInAPackage("com.example.db")).verify();
ApplicationModules.of(Application::class.java, JavaClass.Predicates.resideInAPackage("com.example.db")).verify()

排除项的其他示例:spring-doc.cadn.net.cn

  • com.example.db— 匹配给定包中的所有文件com.example.db.spring-doc.cadn.net.cn

  • com.example.db..— 匹配给定包中的所有文件 (com.example.db) 和所有子软件包 (com.example.db.acom.example.db.b.c).spring-doc.cadn.net.cn

  • ..example..— 比赛a.example,a.example.ba.b.example.c.d,但不是a.exam.bspring-doc.cadn.net.cn

有关可能的匹配器的完整详细信息,请参见 ArchUnit 的 JavaDocPackageMatcher.spring-doc.cadn.net.cn

简单的应用程序模块

应用程序的主包是主应用程序类所在的包。 即 class ,它被注解为@SpringBootApplication,通常包含main(…)用于运行它的方法。 默认情况下,主包的每个直接子包都被视为一个应用程序模块包spring-doc.cadn.net.cn

如果此 package 不包含任何子 package,则认为它是一个简单的 package。 它允许通过使用 Java 的包范围来隐藏其中的代码,以隐藏驻留在其他包中的代码引用的类型,从而不受依赖项注入到这些包中的影响。 因此,该模块的 API 自然由包中的所有公共类型组成。spring-doc.cadn.net.cn

让我们看一个示例安排(表示 public 类型, package-private 类型)。spring-doc.cadn.net.cn

单个库存应用程序模块
 Example
└─  src/main/java
   ├─  example                        (1)
   |  └─  Application.java
   └─  example.inventory              (2)
      ├─  InventoryManagement.java
      └─  SomethingInventoryInternal.java
1 应用程序的 main 软件包example.
2 应用程序模块包inventory.

高级应用程序模块

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

库存和订单应用程序模块
 Example
└─  src/main/java
   ├─  example
   |  └─  Application.java
   ├─  example.inventory
   |  ├─  InventoryManagement.java
   |  └─  SomethingInventoryInternal.java
   ├─  example.order
   |  └─  OrderManagement.java
   └─  example.order.internal
      └─  SomethingOrderInternal.java

在这样的安排中,orderpackage 被视为 API 包。 允许来自其他应用程序模块的代码引用其中的类型。order.internal,就像应用程序模块基础包的任何其他子包一样,被视为内部子包。 这些模块中的代码不得从其他模块引用。 请注意作方法SomethingOrderInternal是公共类型,可能是因为OrderManagement取决于它。 不幸的是,这意味着它也可以从其他包中引用,例如inventory一。 在这种情况下,Java 编译器对于防止这些非法引用没有多大用处。spring-doc.cadn.net.cn

打开应用程序模块

上述安排被认为是封闭的,因为它们只将类型公开给主动选择公开的其他模块。 当将 Spring Modulith 应用于遗留应用程序时,对其他模块隐藏位于嵌套包中的所有类型可能是不够的,或者还需要标记所有这些包以进行公开。spring-doc.cadn.net.cn

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

将应用程序模块声明为打开
@org.springframework.modulith.ApplicationModule(
  type = Type.OPEN
)
package example.inventory;
@org.springframework.modulith.ApplicationModule(
  type = Type.OPEN
)
package example.inventory

将应用程序模块声明为 open 将导致验证发生以下更改:spring-doc.cadn.net.cn

此功能主要用于现有项目的代码库,这些项目正在逐渐转向 Spring Modulith 推荐的打包结构。 在完全模块化的应用程序中,使用开放式应用程序模块通常暗示着次优的模块化和打包结构。

显式应用程序模块依赖项

模块可以通过使用@ApplicationModule注解,通过package-info.java文件。 例如,由于 Kotlin 不支持该文件,因此您还可以对位于应用程序模块的根包中的单个类型使用注释。spring-doc.cadn.net.cn

清单显式配置模块依赖项
@org.springframework.modulith.ApplicationModule(
  allowedDependencies = "order"
)
package example.inventory;
package example.inventory

import org.springframework.modulith.ApplicationModule

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

在这种情况下,inventory 模块中的代码只允许引用 order 模块中的代码(并且最初没有将代码分配给任何模块)。 在验证应用程序模块结构中了解如何监控该情况。spring-doc.cadn.net.cn

命名接口

默认情况下,如高级应用程序模块中所述,应用程序模块的基本包被视为 API 包,因此是唯一允许从其他模块传入依赖项的包。 如果您想将其他软件包公开给其他模块,则需要使用命名接口。 您可以通过注释package-info.java文件,其中包含@NamedInterface或显式注释有@org.springframework.modulith.PackageInfo.spring-doc.cadn.net.cn

封装 SPI 命名接口的封装安排
 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.javaexample.order.spi
@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 模块正在使用它,它可以引用上面声明的命名接口,如下所示:spring-doc.cadn.net.cn

定义专用命名接口的允许依赖关系
@org.springframework.modulith.ApplicationModule(
  allowedDependencies = "order :: spi"
)
package example.inventory;
@org.springframework.modulith.ApplicationModule(
  allowedDependencies = "order :: spi"
)
package example.inventory

请注意我们如何连接命名接口的名称spi通过双冒号::. 在此设置中,允许清单中的代码依赖于SomeSpiInterface和其他位于order.spiinterface 的 Interface,但不在 onOrderManagement例如。 对于没有明确描述依赖关系的模块,应用程序模块根包 SPI 根包都可以访问。spring-doc.cadn.net.cn

如果要表示允许应用程序模块引用所有显式声明的命名接口,则可以使用星号 (),如下所示:*spring-doc.cadn.net.cn

使用星号声明允许的所有已声明命名接口的依赖项
@org.springframework.modulith.ApplicationModule(
  allowedDependencies = "order :: *"
)
package example.inventory;
@org.springframework.modulith.ApplicationModule(
  allowedDependencies = "order :: *"
)
package example.inventory

自定义模块检测

默认情况下,应用程序模块将位于 Spring Boot 应用程序类所在的包的直接子包中。 可以激活另一种检测策略,仅考虑显式注释的包,或者通过 Spring Modulith 的@ApplicationModule或 jMolecules@Module注解。 该策略可以通过配置spring.modulith.detection-strategyexplicitly-annotated.spring-doc.cadn.net.cn

将应用程序模块检测策略切换为仅考虑带 Comments 的软件包
spring.modulith.detection-strategy=explicitly-annotated

如果默认应用程序模块检测策略和手动注释的检测策略都不适用于您的应用程序,则可以通过提供ApplicationModuleDetectionStrategy. 该接口公开了单个方法Stream<JavaPackage> getModuleBasePackages(JavaPackage),并将使用 Spring Boot 应用程序类所在的包调用。 然后,您可以检查驻留在其中的包,并根据命名约定等选择要被视为应用程序模块基础包的包。spring-doc.cadn.net.cn

假设您声明了一个自定义ApplicationModuleDetectionStrategy实现如下:spring-doc.cadn.net.cn

实现自定义ApplicationModuleDetectionStrategy
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-doc.cadn.net.cn

spring.modulith.detection-strategy=example.CustomApplicationModuleDetectionStrategy

自定义应用程序模块排列

Spring Moduliths 允许您通过@Modulithic注解,用于 Spring Boot 应用程序主类。spring-doc.cadn.net.cn

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-doc.cadn.net.cn

Annotation 属性 描述

systemNamespring-doc.cadn.net.cn

要在生成的文档中使用的应用程序的人类可读名称。spring-doc.cadn.net.cn

sharedModulesspring-doc.cadn.net.cn

将具有给定名称的应用程序模块声明为共享模块,这意味着它们将始终包含在应用程序模块集成测试中。spring-doc.cadn.net.cn

additionalPackagesspring-doc.cadn.net.cn

指示 Spring Modulith 将配置的包视为额外的根应用程序包。换句话说,也会为这些触发应用程序模块检测。spring-doc.cadn.net.cn