网站推广公司就去柚米,怎么构建网站,企业网站建设经济效益分析,安徽建站管理系统价格Java 平台模块系统 (JPMS) 提供了更强的封装、更高的可靠性和更好的关注点分离#xff0c;有些同学可能没注意到。
不过呢#xff0c;也是有利有弊。由于模块化应用程序构建在依赖其他模块才能正常工作的模块网络上#xff0c;因此在许多情况下#xff0c;模块彼此紧密耦合…Java 平台模块系统 (JPMS) 提供了更强的封装、更高的可靠性和更好的关注点分离有些同学可能没注意到。
不过呢也是有利有弊。由于模块化应用程序构建在依赖其他模块才能正常工作的模块网络上因此在许多情况下模块彼此紧密耦合。
这可能会让我们认为模块化和松散耦合是不能在同一系统中共存的特性。不过呢他们可以的
接着我们就来深入研究两种众所周知的设计模式我们可以使用它们轻松解耦 Java 模块。
1、创建项目
我们弄个多模块的 Mavene 项目来演示吧。
为了保持代码简单该项目最初将包含两个 Maven 模块每个 Maven 模块将被包装到一个 Java 模块中。
第一个模块将包括一个服务接口以及两个实现——服务提供者。
第二个模块将使用提供程序来解析字符串值。
咱创建一个名为 Demoproject 的项目根目录然后定义一下项目的父 POM
packagingpom/packagingmodulesmoduleservicemodule/modulemoduleconsumermodule/module
/modulesbuildpluginManagementpluginsplugingroupIdorg.apache.maven.plugins/groupIdartifactIdmaven-compiler-plugin/artifactIdversion3.8.1/versionconfigurationsource11/sourcetarget11/target/configuration/plugin/plugins/pluginManagement
/build2、服务模块
出于演示目的让我们使用一种快速而肮脏的方法来实现 servicemodule 模块这样我们就可以清楚地发现此设计中出现的缺陷。
让我们将服务接口和服务提供者公开将它们放在同一个包中并全部导出。这似乎是一个相当不错的设计选择但正如我们稍后将看到的它极大地提高了项目模块之间的耦合程度。
在项目的根目录下我们将创建 servicemodule/src/main/java 目录。然后我们需要定义包 com.baeldung.servicemodule并在其中放置以下 TextService 接口
public interface TextService {String processText(String text);}TextService 接口非常简单接着我们定义服务提供者。
在同一个包中我们添加一个 Lowercase 实现
public class LowercaseTextService implements TextService {Overridepublic String processText(String text) {return text.toLowerCase();}}现在让我们添加一个大写实现
public class UppercaseTextService implements TextService {Overridepublic String processText(String text) {return text.toUpperCase();}}最后在 servicemodule/src/main/java 目录下包含模块描述符 module-info.java
module com.baeldung.servicemodule {exports com.baeldung.servicemodule;
}
3、消费者模块
现在我们需要创建一个使用我们之前创建的服务提供者之一的消费者模块。
让我们添加 com.baeldung.consumermodule.Application 类
public class Application {public static void main(String args[]) {TextService textService new LowercaseTextService();System.out.println(textService.processText(Hello from Baeldung!));}
}现在让我们在源根目录中包含模块描述符 module-info.java它应该是consumermodule/src/main/java
module com.baeldung.consumermodule {requires com.baeldung.servicemodule;
}最后运行它。
但有一个值得注意的重要警告我们不必要地将服务提供者耦合到消费者模块。
由于我们使提供者对外界可见因此消费者模块能够意识到它们。
此外这不利于使软件组件依赖于抽象。
4、服务提供者工厂
我们可以通过仅导出服务接口来轻松消除模块之间的耦合。相比之下服务提供者不会被导出因此对消费者模块来说仍然是隐藏的。消费者模块只能看到服务接口类型。
为了实现这一目标我们需要
1、将服务接口放在单独的包中导出给外界
2、将服务提供者放在不同的包中该包不导出
3、创建一个工厂类并将其导出。消费者模块使用工厂类来查找服务提供者
我们可以将上述步骤概念化为设计模式的形式公共服务接口、私有服务提供者和公共服务提供者工厂。
4.1 公共服务接口
为了清楚地了解此模式的工作原理让我们将服务接口和服务提供者放在不同的包中。接口将被导出但提供程序实现不会被导出。
因此让我们将 TextService 移至一个名为 com.baeldung.servicemodule.external 的新包。
4.2 私服提供者
然后我们同样将 LowercaseTextService 和 UppercaseTextService 移动到 com.baeldung.servicemodule.internal。
4.3 公共服务提供者工厂
由于服务提供者类现在是私有的无法从其他模块访问因此我们将使用公共工厂类来提供一种简单的机制消费者模块可以使用该机制来获取服务提供者的实例。
在 com.baeldung.servicemodule.external 包中我们定义 TextServiceFactory 类
public class TextServiceFactory {private TextServiceFactory() {}public static TextService getTextService(String name) {return name.equalsIgnoreCase(lowercase) ? new LowercaseTextService(): new UppercaseTextService();}}当然我们可以使工厂类稍微复杂一些。为了简单起见服务提供者只是根据传递给 getTextService() 方法的字符串值创建的。
现在让我们替换 module-info.java 文件以仅导出外部包
module com.baeldung.servicemodule {exports com.baeldung.servicemodule.external;
}请注意我们仅导出服务接口和工厂类。这些实现是私有的因此它们对其他模块不可见。
4.4 应用类
现在让我们重构 Application 类以便它可以使用服务提供者工厂类
public static void main(String args[]) {TextService textService TextServiceFactory.getTextService(lowercase);System.out.println(textService.processText(Hello from Baeldung!));
}
然后运行它。
通过将服务接口设为公开将服务提供者设为私有我们可以通过简单的工厂类有效地解耦服务和消费者模块。
当然没有任何模式是灵丹妙药。与往常一样我们应该首先分析我们的用例是否适合。
5、服务和消费者模块
JPMS 通过provides…with 和uses 指令为开箱即用的服务和消费者模块提供支持。
因此我们可以使用这个功能来解耦模块而无需创建额外的工厂类。
为了让服务和消费者模块协同工作我们需要执行以下操作
1、将服务接口放在模块中模块导出接口
2、将服务提供者放在另一个模块中 - 提供者被导出
3、在提供者的模块描述符中指定我们想要使用provides…with指令提供TextService实现
4、将 Application 类放置在它自己的模块中——消费者模块
5、在消费者模块的模块描述符中指定该模块是带有使用指令的消费者模块
6、使用消费者模块中的 Service Loader API 来查找服务提供者
这种方法非常强大因为它利用了服务和消费者模块带来的所有功能。但这也有点棘手。
一方面我们让消费者模块只依赖于服务接口而不依赖于服务提供者。另一方面我们甚至可以根本不定义服务提供者应用程序仍然可以编译。
5.1 父模块
为了实现这个模式我们还需要重构父 POM 和现有模块。
由于服务接口、服务提供者和消费者现在将位于不同的模块中我们首先需要修改父 POM 的 部分以反映这个新结构
modulesmoduleservicemodule/modulemoduleprovidermodule/modulemoduleconsumermodule/module
/modules5.2 服务模块
我们的 TextService 接口将返回 com.baeldung.servicemodule。
我们将相应地更改模块描述符
module com.baeldung.servicemodule {exports com.baeldung.servicemodule;
}5.3 提供者模块
如前所述提供程序模块用于我们的实现因此现在让我们将 LowerCaseTextService 和 UppercaseTextService 放在这里。我们将它们放入一个名为 com.baeldung.providermodule 的包中。
最后我们添加一个 module-info.java 文件
module com.baeldung.providermodule {requires com.baeldung.servicemodule;provides com.baeldung.servicemodule.TextService with com.baeldung.providermodule.LowercaseTextService;
}5.4 消费者模块
现在让我们重构消费者模块。首先我们将应用程序放回 com.baeldung.consumermodule 包中。
接下来我们将重构 Application 类的 main() 方法以便它可以使用 ServiceLoader 类来发现适当的实现
public static void main(String[] args) {ServiceLoaderTextService services ServiceLoader.load(TextService.class);for (final TextService service: services) {System.out.println(The service service.getClass().getSimpleName() says: service.parseText(Hello from Baeldung!));}
}最后我们将重构 module-info.java 文件 module com.baeldung.consumermodule {requires com.baeldung.servicemodule;uses com.baeldung.servicemodule.TextService;
}然后运行它
正如我们所看到的实现这种模式比使用工厂类的模式稍微复杂一些。即便如此额外的努力也会通过更灵活、松散耦合的设计得到高度回报。
费者模块依赖于抽象并且在运行时也很容易插入不同的服务提供者。
6、总结
我们学习了如何实现两种模式来解耦 Java 模块。
这两种方法都使消费者模块依赖于抽象这始终是软件组件设计中所需的功能。
当然每一种都有其优点和缺点。对于第一个我们得到了很好的解耦但我们必须创建一个额外的工厂类。
对于第二个为了使模块解耦我们必须创建一个额外的抽象模块并使用 Service Loader API 添加新的间接级别。