对于最新的稳定版本,请使用 Spring Modulith 1.2.1

对于最新的稳定版本,请使用 Spring Modulith 1.2.1

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

应用模块

在 Spring Boot 应用程序中,应用程序模块是一个功能单元,由以下部分组成:

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

  • 不应由其他模块访问的内部实现组件。

  • 其他模块以 Spring Bean 依赖、监听的应用程序事件和公开的配置属性的形式对 API 的引用,通常称为必需接口

Spring Moduliths 在 Spring Boot 应用程序中提供了不同的模块表达方式,主要区别在于整体排列中涉及的复杂程度。 这允许开发人员从简单开始,并在需要时自然地转向更复杂的方法。

简单应用模块

应用程序的主包是主应用程序类所在的包。 这就是类,它带有注释,通常包含用于运行它的方法。 默认情况下,主包的每个直接子包都被视为一个应用程序模块包@SpringBootApplicationmain(…)

如果此包不包含任何子包,则将其视为简单包。 它允许通过使用 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

在这种安排中,该包被视为 API 包。 允许来自其他应用程序模块的代码引用其中的类型。,就像应用程序模块基础包的任何其他子包一样,被视为内部子包。 其中的代码不得从其他模块引用。 注意,公共类型如何,可能是因为取决于它。 不幸的是,这意味着它也可以从其他包(例如一个包)中引用。 在这种情况下,Java 编译器对于防止这些非法引用没有多大用处。orderorder.internalSomethingOrderInternalOrderManagementinventory

显式应用程序模块依赖项

模块可以选择使用类型上的注释来声明其允许的依赖项。@ApplicationModulepackage-info.java

显式配置模块依赖项的清单
  • Java

  • Kotlin

@org.springframework.modulith.ApplicationModule(
  allowedDependencies = "order"
)
package example.inventory;
@org.springframework.modulith.ApplicationModule(
  allowedDependencies = "order"
)
package example.inventory

在这种情况下,库存模块中的代码只允许引用订单模块中的代码(并且代码一开始就没有分配给任何模块)。 在验证应用程序模块结构中了解如何监视它。

类型ApplicationModules

Spring Moduliths 允许检查代码库,以根据给定的排列和可选配置派生应用程序模块模型。 该工件包含可指向 Spring Boot 应用程序类的项:spring-modulith-coreApplicationModules

创建应用程序模块模型
  • Java

  • Kotlin

var modules = ApplicationModules.of(Application.class);
var modules = ApplicationModules.of(Application::class)

为了了解所分析的排列情况,我们可以将整个模型中包含的各个模块写入控制台:

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

命名接口

默认情况下,如高级应用程序模块中所述,应用程序模块的基础包被视为 API 包,因此是唯一允许来自其他模块的传入依赖项的包。 如果您想向其他模块公开其他包,则需要使用命名接口。 您可以通过使用 .package-info.java@NamedInterface

封装 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
  • Java

  • Kotlin

@org.springframework.modulith.NamedInterface("spi")
package example.order.spi;
@org.springframework.modulith.NamedInterface("spi")
package example.order.spi

该声明的效果是双重的:首先,允许其他应用程序模块中的代码引用 。 应用程序模块能够在显式依赖项声明中引用命名接口。 假设清单模块正在使用它,它可以像这样引用上面声明的命名接口:SomeSpiInterface

  • Java

  • Kotlin

@org.springframework.modulith.ApplicationModule(
  allowedDependencies = "order::spi"
)
package example.inventory;
@org.springframework.modulith.ApplicationModule(
  allowedDependencies = "order::spi"
)
package example.inventory

请注意我们如何通过双冒号连接命名接口的名称。 在此设置中,清单中的代码将被允许依赖于接口中的其他代码,但不允许依赖于例如。 对于没有明确描述依赖关系的模块,应用程序模块根包 SPI 模块都可以访问。spi::SomeSpiInterfaceorder.spiOrderManagement

自定义模块检测

如果默认应用程序模块模型不适用于您的应用程序,则可以通过提供 的实现来自定义模块的检测。 该接口公开了一个方法,并将与Spring Boot应用程序类所在的包一起调用。 然后,您可以检查驻留在其中的包,并根据命名约定等选择要被视为应用程序模块基础包的包。ApplicationModuleDetectionStrategyStream<JavaPackage> getModuleBasePackages(JavaPackage)

假设您声明了一个自定义实现,如下所示: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
1 应用程序的主包。example
2 应用程序模块包。inventory