1. 概述
Java 平台模块系统(JPMS)带来了更强的封装性、更高的可靠性以及更清晰的职责划分。但这些特性也带来了模块间耦合度变高的问题。
很多开发者会误以为“模块化”与“松耦合”无法共存。其实不然,本文将介绍两种设计模式,帮助我们在模块化 Java 应用中实现模块间的松耦合。
2. 父项目结构
我们以一个多模块 Maven 项目为例进行讲解。该项目包含三个模块:
servicemodule
:定义服务接口providermodule
:实现服务接口consumermodule
:使用服务接口
父 POM 的配置如下:
<packaging>pom</packaging>
<modules>
<module>servicemodule</module>
<module>providermodule</module>
<module>consumermodule</module>
</modules>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
📌 注意:
- 项目使用 Java 11,需要 Maven 3.5.0 以上版本
- 推荐使用最新版的 Maven Compiler Plugin(至少 3.8.0)
3. 服务模块
我们先定义一个简单的服务接口和两个实现类:
// TextService.java
public interface TextService {
String processText(String text);
}
// LowercaseTextService.java
public class LowercaseTextService implements TextService {
@Override
public String processText(String text) {
return text.toLowerCase();
}
}
// UppercaseTextService.java
public class UppercaseTextService implements TextService {
@Override
public String processText(String text) {
return text.toUpperCase();
}
}
模块描述文件:
module com.baeldung.servicemodule {
exports com.baeldung.servicemodule;
}
⚠️ 问题:此时服务实现类是对外暴露的,导致消费者模块直接依赖具体实现,不符合“依赖抽象”的设计原则。
4. 消费者模块
消费者模块直接使用具体的服务实现类:
public class Application {
public static void main(String[] args) {
TextService textService = new LowercaseTextService();
System.out.println(textService.processText("Hello from Baeldung!"));
}
}
模块描述文件:
module com.baeldung.consumermodule {
requires com.baeldung.servicemodule;
}
输出结果为:
hello from baeldung!
✅ 功能正常,但 ❌ 存在紧耦合:消费者模块直接依赖服务实现类,不利于扩展和维护。
5. 服务提供工厂模式
我们通过工厂类隐藏服务实现细节,只暴露接口和工厂类,从而实现模块解耦。
5.1 公共服务接口
将接口移到独立包中并导出:
package com.baeldung.servicemodule.external;
public interface TextService {
String processText(String text);
}
5.2 私有服务实现
实现类移至内部包,不导出:
package com.baeldung.servicemodule.internal;
public class LowercaseTextService implements TextService {
@Override
public String processText(String text) {
return text.toLowerCase();
}
}
package com.baeldung.servicemodule.internal;
public class UppercaseTextService implements TextService {
@Override
public String processText(String text) {
return text.toUpperCase();
}
}
5.3 公共工厂类
package com.baeldung.servicemodule.external;
public class TextServiceFactory {
private TextServiceFactory() {}
public static TextService getTextService(String name) {
return name.equalsIgnoreCase("lowercase") ? new LowercaseTextService() : new UppercaseTextService();
}
}
模块描述文件仅导出接口和工厂类:
module com.baeldung.servicemodule {
exports com.baeldung.servicemodule.external;
}
5.4 消费者模块重构
使用工厂类获取服务实例:
public class Application {
public static void main(String[] args) {
TextService textService = TextServiceFactory.getTextService("lowercase");
System.out.println(textService.processText("Hello from Baeldung!"));
}
}
输出结果:
hello from baeldung!
✅ 优点:
- 消费者模块只依赖接口和工厂类
- 实现类对消费者不可见,实现了解耦
❌ 缺点:
- 需要手动编写工厂类
- 工厂类逻辑可能变得复杂
6. 服务提供者和服务消费者模块
Java 原生支持服务提供者和服务消费者模块机制,通过 provides...with
和 uses
指令实现模块解耦。
6.1 项目结构调整
父 POM 更新为:
<modules>
<module>servicemodule</module>
<module>providermodule</module>
<module>consumermodule</module>
</modules>
6.2 服务模块
仅导出接口:
module com.baeldung.servicemodule {
exports com.baeldung.servicemodule;
}
6.3 提供者模块
实现接口并注册为服务提供者:
module com.baeldung.providermodule {
requires com.baeldung.servicemodule;
provides com.baeldung.servicemodule.TextService with com.baeldung.providermodule.LowercaseTextService;
}
6.4 消费者模块
使用 ServiceLoader
动态加载服务:
public static void main(String[] args) {
ServiceLoader<TextService> services = ServiceLoader.load(TextService.class);
for (final TextService service : services) {
System.out.println("The service " + service.getClass().getSimpleName() +
" says: " + service.processText("Hello from Baeldung!"));
}
}
模块描述文件:
module com.baeldung.consumermodule {
requires com.baeldung.servicemodule;
uses com.baeldung.servicemodule.TextService;
}
输出结果:
The service LowercaseTextService says: hello from baeldung!
✅ 优点:
- 无需手动编写工厂类
- 支持运行时动态加载服务实现
- 更符合“依赖抽象”的设计原则
❌ 缺点:
- 配置较复杂
- 需要额外模块管理服务实现
7. 总结
我们介绍了两种实现 Java 模块解耦的设计方式:
方式 | 优点 | 缺点 |
---|---|---|
服务工厂模式 | 实现简单,控制灵活 | 需要手动编写工厂类 |
服务提供者/消费者模块 | 原生支持,高度解耦,支持运行时插拔 | 配置复杂,学习成本高 |
📌 建议:
- 对于小型项目或快速原型开发,推荐使用服务工厂模式
- 对于大型模块化系统,尤其是需要插件化架构的项目,推荐使用服务提供者/消费者模块机制
所有示例代码已上传至 GitHub,欢迎参考:GitHub 项目地址