通过上节内容,我们了解了 Spring Modulith 默认的模块封装机制:位于模块内部子包(如 internal
)的类型默认是不可见的,外部模块无法直接访问,从而保证了模块的内聚性。例如,在我们的示例中,orderService
默认情况下是无法直接访问 notification
模块的 internal
包下的 NiujiaoNotificationService
的。
但在某些特定场景下,我们可能需要更灵活地控制模块的访问权限,或者需要绕过默认的封装限制。Spring Modulith 提供了 开放应用程序模块 (Open Application Modules) 和 显式应用程序模块依赖 (Explicit Application Module Dependencies) 等机制来满足这些需求。
Open Application Modules (开放应用程序模块)
有时,我们可能需要完全开放一个模块的内部实现,允许其他模块自由访问其内部的任何类型,即使它们位于内部子包中。这通常用于处理遗留模块、共享基础代码模块,或者在模块边界尚不明确时的过渡阶段。
要将一个应用程序模块声明为“开放”,您需要在该模块根包的 package-info.java
文件上使用 @ApplicationModule(type = Type.OPEN)
注解。
// notification\package-info.java
@org.springframework.modulith.ApplicationModule(type = org.springframework.modulith.ApplicationModule.Type.OPEN)
package org.niujiao.spring_modulith_tutorials.notification;
代码示例:让 OrderService 直接访问 NiujiaoNotificationService
在将 notification
模块设置为 OPEN
后,order
模块现在就可以直接依赖 NiujiaoNotificationService
了。修改 OrderService
:
// order/OrderService.java
package org.niujiao.spring_modulith_tutorials.order;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.niujiao.spring_modulith_tutorials.notification.Notification;
// 直接引入 notification 模块的内部实现类
import org.niujiao.spring_modulith_tutorials.notification.internal.NiujiaoNotificationService;
import org.springframework.stereotype.Service;
@RequiredArgsConstructor
@Service
@Slf4j
public class OrderService {
// 现在直接依赖 notification 模块的内部服务
private final NiujiaoNotificationService niujiaoNotificationService;
public void createOrder(Order order) {
log.info("Order Module: Attempting to create order.");
order.setNo(System.currentTimeMillis() + "");
log.info("Order Module: Order created successfully, order number: {}", order.getNo());
Notification notification = new Notification("order_create",
"Order created, order no is " + order.getNo() + ". Items: " + order.getItems());
// 直接调用内部服务
niujiaoNotificationService.sendInternalNotification(
String.format("Notification for type '%s' with content: '%s'", notification.getType(), notification.getContent()));
log.info("Order Module: Notification sent for order creation.");
}
}
运行测试验证:
运行 SpringModulithTutorialsApplicationTests
中的 verifyApplicationModuleModel()
测试方法。
// SpringModulithTutorialsApplicationTests.java
@Test
void verifyApplicationModuleModel() {
System.out.println("\n--- 验证 Spring Modulith 模块结构 ---");
ApplicationModules modules = ApplicationModules.of(SpringModulithTutorialsApplication.class);
System.out.println("--- 检测到的模块信息 ---");
modules.forEach(System.out::println);
System.out.println("--------------------");
modules.verify(); // 验证应该会成功
System.out.println("--- 模块结构验证成功!---");
System.out.println("--- 验证 Spring Modulith 模块结构完毕 ---\n");
}
测试应该会成功通过。观察控制台打印的模块信息,您会发现 notification
模块的内部 Bean(如 NiujiaoNotificationService
)现在可能被标记为可以从外部访问(具体标记取决于 Modulith 版本和日志格式)。
Open Application Modules 的效果:
将一个模块声明为 OPEN
会对模块验证产生以下影响:
- 其他模块通常允许访问该开放模块的任何内部类型,包括位于内部子包中的类型。
- 该模块的所有类型,包括位于其根包子包中的类型,都会被添加到未命名(<<UNNAMED>>)的命名接口中,除非它们已被显式分配到其他命名接口。
注意: OPEN
模块会绕过 Spring Modulith 默认提供的强大的封装性检查,这通常不推荐作为常态。它更多用于特殊情况或逐步重构。
Named Interfaces(命名接口)
命名接口它是更精细地定义模块对外 API 的方式。为 notification.internal
包添加的 @NamedInterface("internal")
:
// notification\internal\package-info.java
@org.springframework.modulith.NamedInterface("internal")
package org.niujiao.spring_modulith_tutorials.notification.internal;
这个注解使得 notification.internal
包中的 NiujiaoNotificationService
成为了 notification
模块中名为 "internal"
的命名接口的一部分。
模块信息中的命名接口:
运行测试时打印的模块信息会显示这些命名接口:
# Notification
> Logical name: notification
> Base package: org.niujiao.spring_modulith_tutorials.notification
> Excluded packages: none
> Named interfaces:
+ NamedInterface: name=<<UNNAMED>>, types=[ o.n.s.n.Notification, o.n.s.n.NotificationFacadeService ] // 默认命名接口
+ NamedInterface: name=internal, types=[ o.n.s.n.i.NiujiaoNotificationService ] // 显式命名接口
> Spring beans:
+ ….NotificationFacadeService
+ ….internal.NiujiaoNotificationService
这里清晰地列出了 notification
模块暴露的两种命名接口。
Explicit Application Module Dependencies (显式应用程序模块依赖)
消费方模块可以显式地控制允许其依赖哪些模块的哪些部分。这正是通过在消费方模块(如 order
模块)的 @ApplicationModule
注解中设置 allowedDependencies
属性来实现的。这种方式让依赖关系更加清晰和受控。
回顾一下 notification
模块的信息,我们知道它默认会暴露根包下的公共类型(如 Notification
和 NotificationFacadeService
)作为其 API 的一部分。Spring Modulith 将这部分自动识别为名为 <<UNNAMED>>
的命名接口。
接下来,我们将看到如何在 order
模块上配置 allowedDependencies
,以显式地声明它允许依赖 notification
模块的这些暴露内容。
代码示例:order 模块显式依赖 notification 模块
首先,将 notification
模块恢复为默认的 CLOSED
类型(移除或注释掉 notification/package-info.java
中的 type = Type.OPEN
)。确保 OrderService
当前依赖的是 notification
模块默认接口中的类型,例如 Notification
或 NotificationFacadeService
。
允许依赖默认接口(根包公共类型):
如果您希望 order
模块能够依赖 notification
模块根包下的公共类型(如 Notification
或 NotificationFacadeService
),只需要在 allowedDependencies
中使用目标模块的名称。在order
包下创建一个 package-info.java
文件
// order/package-info.java
@org.springframework.modulith.ApplicationModule(
allowedDependencies = { "notification" } // 允许依赖 notification 模块的默认接口 (<<UNNAMED>>)
)
package org.niujiao.spring_modulith_tutorials.order;
// 定义 order 模块并允许依赖 notification 模块的默认接口
在这种配置下,如果 OrderService.java
依赖了 org.niujiao.spring_modulith_tutorials.notification.Notification
或 org.niujiao.spring_modulith_tutorials.notification.NotificationFacadeService
,运行测试方法 verifyApplicationModuleModel()
将会成功。
如果 OrderService
尝试依赖 notification.internal.NiujiaoNotificationService
,则会验证失败,因为 internal
接口未被允许。
允许依赖特定命名接口:
如果您只希望 order
模块依赖 notification
模块中名为 "internal"
的命名接口(对应 notification.internal
包中的类型),您可以在 allowedDependencies
中这样指定:
// order/package-info.java
@org.springframework.modulith.ApplicationModule(
allowedDependencies = { "notification :: internal" } // 允许依赖 notification 模块的 "internal" 命名接口
)
package org.niujiao.spring_modulith_tutorials.order;
在这种配置下,只有 notification.internal
包下的类型(如 NiujiaoNotificationService
,因为它属于 internal
命名接口)才能被 order
模块依赖。如果 OrderService
依赖 notification.Notification
或 NotificationFacadeService
,则会验证失败。
允许依赖多个目标:
您可以在 allowedDependencies
数组中列出多个允许的目标。例如,允许依赖 notification
模块的默认接口和 internal
接口:
// order/package-info.java
@org.springframework.modulith.ApplicationModule(
allowedDependencies = { "notification", "notification :: internal" } // 允许依赖 notification 的默认接口 和 "internal" 接口
)
package org.niujiao.spring_modulith_tutorials.order;
如果您希望允许依赖目标模块所有显式声明的命名接口(以及根据规则可能包含的默认接口),可以使用 :: *
通配符:
// order/package-info.java
@org.springframework.modulith.ApplicationModule(
allowedDependencies = { "notification :: *" } // 允许依赖 notification 模块的所有命名接口 (包括 <<UNNAMED>> 和 internal)
)
package org.niujiao.spring_modulith_tutorials.order;
// 允许依赖 notification 模块的所有命名接口
感谢您的阅读!希望这部分内容对您有所启发。如果您觉得有价值,请不吝点赞支持,并在评论区留下您的想法,一起交流学习!别忘了关注我,获取后续更精彩的教程内容。您的每一个肯定和互动,都是我持续分享知识的动力!