该版本仍在开发中,尚未被视为稳定。最新稳定版请使用Spring Modulith 2.0.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 Modulith 提供了在 Spring Boot 应用中表达模块的不同方式,主要区别在于整体布局的复杂度。 这使得开发者可以从简单开始,并在需要时自然过渡到更复杂的方式。spring-doc.cadn.net.cn

应用模块类型

Spring Modulith 允许检查代码库,基于给定的排列和可选配置推导应用模块模型。 这Spring-模块-核心文件包含应用模块可以指向一个 Spring Boot 应用类:spring-doc.cadn.net.cn

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

模块将包含从代码库派生的应用模块配置的内存表示。 其中哪些部分会被检测为模块,取决于该类所指向的包底下的 Java 包结构。 在简单应用模块中了解更多默认安排的信息。 高级配置和定制选项详见高级应用模块spring-doc.cadn.net.cn

为了了解分析后的排列,我们可以直接将整体模型中包含的各个模块写入控制台: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 公元前).spring-doc.cadn.net.cn

  • ..例。。—— 比赛a.示例,a.example.bA.B.EXAMPLE.C.D,但又不是A.考试.Bspring-doc.cadn.net.cn

关于可能匹配器的完整细节可见于 ArchUnit 的 JavaDoc 中PackageMatcher.spring-doc.cadn.net.cn

简单应用模块

应用的主包就是主应用类所在的包。 这就是那个类,注释为@SpringBootApplication通常包含主要(...)运行时使用的方法。 默认情况下,主包的每个直接子包都被视为应用模块包spring-doc.cadn.net.cn

如果该包不包含任何子包,则视为简单包。 它允许通过利用 Java 的包作用域隐藏代码,防止其他包中的代码引用类型,从而避免依赖注入。 因此,模块的 API 自然包含包中所有的公共类型。spring-doc.cadn.net.cn

让我们来看一个示例排列(表示公用类型,包私有类型)。spring-doc.cadn.net.cn

单一库存应用模块
 Example
╰─  src/main/java
   ├─  example                        (1)
   │  ╰─  Application.java
   ╰─  example.inventory              (2)
      ├─  InventoryManagement.java
      ╰─  SomethingInventoryInternal.java
1 应用的主包示例.
2 应用模块包库存.

高级应用模块

如果应用模块包包含子包,这些子包中的类型可能需要公开,以便从同一模块的代码中引用。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

在这样的安排中,次序package 被视为 API 包。 其他应用模块的代码允许引用该模块中的类型。秩序。内部,就像应用模块基础包的其他子包一样,都被视为内部包。 这些模块中的代码不得引用其他模块。 注意内在秩序是公共类型,很可能因为订单管理要看情况。 不幸的是,这意味着它也可以从其他包中引用,比如库存一。 在这种情况下,Java 编译器在防止这些非法引用方面帮助不大。spring-doc.cadn.net.cn

嵌套应用模块

从版本 1.3 起,Spring Modulith 应用模块可以包含嵌套模块。 这允许在模块包含部分需要逻辑分离时,对内部结构进行管理。 要定义嵌套应用模块,应明确注释那些应由 组成的包@ApplicationModule.spring-doc.cadn.net.cn

 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

在这个例子中库存上述描述的应用模块。 这@ApplicationModule关于嵌 套package 又使该模块变成嵌套应用模块。 在该安排中,适用以下访问规则:spring-doc.cadn.net.cn

  • 嵌套中的代码只能从Inventory或由嵌套在Inventory中的兄弟应用模块暴露的任何类型中获得。spring-doc.cadn.net.cn

  • 嵌套模块中的任何代码都可以访问父模块中的代码,甚至是内部的。 也就是说,两者兼有嵌套API(NestedApi)嵌套内部可进入inventory.internal.SomethingInventoryInternal.spring-doc.cadn.net.cn

  • 嵌套模块中的代码也可以访问顶层应用模块的暴露类型。 任何嵌 套(或任何子软件包)可以访问订单管理.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;
package example.inventory

import org.springframework.modulith.ApplicationModule
import org.springframework.modulith.PackageInfo

@ApplicationModule(
  type = Type.OPEN
)
@PackageInfo
class ModuleMetadata {}

将应用模块宣布为开放状态,将导致验证发生以下变化: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 {}

在这种情况下,库存模块内的代码只能引用订单模块中的代码(以及最初未分配给任何模块的代码)。 了解如何监控这一点,请参阅《验证应用模块结构》。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 {}

该声明的效果有两个方面:首先,其他应用模块中的代码可以引用某种Spi接口. 应用模块能够在显式依赖声明中引用命名接口。 假设库存模块利用了这个接口,它可以像这样指代上述声明的命名接口:spring-doc.cadn.net.cn

定义允许的专用命名接口的依赖
@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通过双冒号::. 在这种设置下,库存中的代码可以依赖于某种Spi接口以及其他存在于order.spi接口,但未开启订单管理例如。 对于没有明确描述依赖的模块,应用模块的根包和SPI包都可访问。spring-doc.cadn.net.cn

如果你想表达应用模块允许引用所有明确声明的命名接口,可以使用星号()如下:*spring-doc.cadn.net.cn

使用星号声明所有已声明命名接口的允许依赖
@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-doc.cadn.net.cn

定制应用模块排列

Spring Modulith 允许你围绕你通过@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

注释属性 描述

系统名称spring-doc.cadn.net.cn

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

共享模块spring-doc.cadn.net.cn

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

附加软件包spring-doc.cadn.net.cn

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

模块检测的定制化

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

将应用模块检测策略切换为仅考虑带注释的包
spring.modulith.detection-strategy=explicitly-annotated

如果默认的应用模块检测策略和手动注释的策略都不适用于你的应用,可以通过提供应用模块检测策略. 该接口暴露了单一方法Stream<JavaPackage> getModuleBasePackages(JavaPackage)并且会被调用,包含 Spring Boot 应用类所包含的包。 然后你可以检查这些包里的包,并根据命名规范等方式选择哪些包被视为应用模块基础包。spring-doc.cadn.net.cn

假设你宣告了一个习俗应用模块检测策略实现方式如下:spring-doc.cadn.net.cn

实现自定义应用模块检测策略
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 Modulith 运行时组件(例如应用模块初始化器S,或生产准备特性,如执行器和可观测支持),你需要明确声明以下为编译时依赖:spring-doc.cadn.net.cn

<dependency>
  <groupId>org.springframework.modulith</groupId>
  <artifactId>spring-modulith-core</artifactId>
</dependency>
dependencies {
  implementation 'org.springframework.modulith:spring-modulith-core'
}

来自其他软件包的贡献应用模块

@Modulithic允许定义附加软件包要触发注释类中非应用模块包的检测,其使用需要事先了解这些包。 自1.3版本起,Spring Modulith支持通过应用模块源应用模块SourceFactory抽象。 后者的实现可以在Spring。工厂文件位于元步兵.spring-doc.cadn.net.cn

org.springframework.modulith.core.ApplicationModuleSourceFactory=example.CustomApplicationModuleSourceFactory

这样的工厂可以返回任意包名,以获得应用模块检测策略应用,或显式返回用于创建模块的包。spring-doc.cadn.net.cn

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");
  }
}

上述例子会使用com.acme.toscan检测其中显式声明的模块,并从 创建应用模块com.acme.module. 这些包裹返回的名称随后会被翻译为应用模块源通过对应的getApplicationModuleSource(...)暴露的风味应用模块检测策略.spring-doc.cadn.net.cn

自定义命名接口检测

如果你想用编程方式描述某个应用模块的命名接口,请注册应用模块检测策略此处描述,并使用detectNamedInterfaces(JavaPackage, ApplicationModuleInformation)实现自定义发现算法。spring-doc.cadn.net.cn

使用自定义接口来自定义命名接口检测应用模块检测策略
package example;

class CustomApplicationModuleDetectionStrategy implements ApplicationModuleDetectionStrategy {

  @Override
  public Stream<JavaPackage> getModuleBasePackages(JavaPackage basePackage) {
    // Your module detection goes here
  }

  @Override
  NamedInterfaces detectNamedInterfaces(JavaPackage basePackage, ApplicationModuleInformation information) {
    return NamedInterfaces.builder()
        .recursive()
        .matching("api")
        .build();
  }
}
package example

class CustomApplicationModuleDetectionStrategy : ApplicationModuleDetectionStrategy {

  override fun getModuleBasePackages(basePackage: JavaPackage): Stream<JavaPackage> {
    // Your module detection goes here
  }

  override fun detectNamedInterfaces(basePackage: JavaPackage, information: ApplicationModuleInformation): NamedInterfaces {
    return NamedInterfaces.builder()
        .recursive()
        .matching("api")
        .build()
  }
}

detectNamedInterfaces(...)如上所示的实现,我们构建了一个命名接口所有名为应用程序接口在给定应用模块的基础包下方。 这架构工人API 还会提供额外的方法,用于选择包作为命名接口,或显式地将它们排除在该接口之外。 注意,构建器始终包含未命名的命名接口,包含应用模块基础包中所有公共方法,因为该接口是应用模块的必需。spring-doc.cadn.net.cn

对于更手动的设置命名接口务必查看其工厂方法以及暴露的命名接口.spring-doc.cadn.net.cn