使用 provides/uses 提供服务
借助服务加载器实现解耦
使用 provides/uses 提供服务 是 CoddyKit 上的免费 Java Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。
通过服务实现解耦
模块可以使用服务加载器模式进行协作,而不必依赖彼此的实现。
一个模块声明它uses某个服务接口,另一个模块声明它provides一个实现。JVM 会将它们连接起来。
三个角色
一个服务包含三个参与方:
- 服务接口:契约,位于某个模块中
- 提供方:提供实现的模块
- 使用方:在运行时加载实现的模块
服务接口
首先定义契约。它只是一个位于已导出包中的普通接口,因此提供方和使用方都可以引用它。
package com.example.spi;
public interface Codec {
String name();
byte[] encode(String input);
}使用方声明服务使用关系
使用方模块声明它uses该接口。这会告诉 JPMS,该模块将通过 ServiceLoader 查找实现。
module com.example.app {
requires com.example.spi;
uses com.example.spi.Codec;
}提供方声明实现关系
提供方模块使用 provides ... with ... 声明它实现的接口,并写出具体类的名称。
module com.example.base64 {
requires com.example.spi;
provides com.example.spi.Codec
with com.example.base64.Base64Codec;
}提供方的实现
实现类就是普通的 Java 类。它必须是公共的,并且提供公共无参构造方法或公共静态 provider() 方法。
package com.example.base64;
import com.example.spi.Codec;
import java.util.Base64;
public class Base64Codec implements Codec {
public String name() { return "base64"; }
public byte[] encode(String input) {
return Base64.getEncoder().encode(input.getBytes());
}
}使用 ServiceLoader 加载
运行时,使用方会通过 ServiceLoader.load(Codec.class) 请求所有可用的提供方并遍历它们,而不需要在编译时链接到任何具体实现。
import com.example.spi.Codec;
import java.util.ServiceLoader;
public class App {
public static void main(String[] args) {
ServiceLoader<Codec> loader = ServiceLoader.load(Codec.class);
for (Codec codec : loader) {
System.out.println("Found codec: " + codec.name());
}
}
}这一机制为何强大
使用方从不指定提供方类。只要将新的提供方模块放到模块路径上,系统就会自动发现它。
这使插件架构和清晰的依赖反转成为可能。
提供方工厂
如果构造过程需要额外逻辑,请公开一个公共静态 provider() 方法,而不是使用无参构造方法。JPMS 会调用该方法来获取实例。
package com.example.base64;
import com.example.spi.Codec;
public class CodecFactory {
public static Codec provider() {
return new Base64Codec();
}
}多个提供方
许多模块可以分别提供同一个接口。ServiceLoader 会返回所有这些提供方,让您的使用方能够按名称、优先级或功能进行选择。
您可以添加或移除提供方,而无需修改使用方代码。
遍历提供方流
从 Java 9 开始,ServiceLoader 提供了 stream(),它会生成 Provider 句柄,您可以在实例化之前对其进行筛选,这在构造过程开销较大时很有用。
import com.example.spi.Codec;
import java.util.ServiceLoader;
public class App {
public static void main(String[] args) {
Codec chosen = ServiceLoader.load(Codec.class).stream()
.map(ServiceLoader.Provider::get)
.filter(c -> c.name().equals("base64"))
.findFirst()
.orElseThrow();
System.out.println("Using: " + chosen.name());
}
}快速检查
请回忆每个角色使用哪个指令。
回顾
您已经学习了模块服务模式:
- 三个角色:服务接口、提供方、使用方
- 使用方:
uses;提供方:provides ... with - 通过
ServiceLoader在运行时加载实现 - 提供方需要公共无参构造方法或静态
provider()
下一步:将现有项目迁移到模块。
常见问题解答
「使用 provides/uses 提供服务」课时是免费的吗?
是的 — 「使用 provides/uses 提供服务」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。
「使用 provides/uses 提供服务」这节课中我会学到什么?
借助服务加载器实现解耦 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Java Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「使用 provides/uses 提供服务」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Java Academy 课中编写并运行代码吗?
能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- module-info.java
- requires 与 exports
- 使用 provides/uses 提供服务
- 迁移到模块