0Pricing
Java Academy · 课时

使用 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. module-info.java
  2. requires 与 exports
  3. 使用 provides/uses 提供服务
  4. 迁移到模块
← 返回 Java Academy