Java 服务提供者接口

最后更新: 2024-10-01 22:56:33 +0800 +0800
tags: Java | Spi
原文以英文撰写。您正在阅读AI翻译版本。

Java SPI 将服务契约与一个或多个提供者以及选择提供者的使用者分离。使用者依赖服务类型并使用ServiceLoader,因此无需让应用依赖具体类,即可在部署时添加提供者。

类路径服务 #

在模块出现之前,提供者 JAR 会在 META-INF/services/<fully.qualified.ServiceName>中列出实现。UTF-8 文件每行包含一个提供者类名;允许使用空行和#注释。每个提供者都必须能被用于发现的加载器访问,并提供一个 public 无参构造函数。

java
ServiceLoader<Codec> codecs = ServiceLoader.load(Codec.class);
for (Codec codec : codecs) {
    if (codec.supports(mediaType)) return codec;
}

发现和提供者实例化都是延迟执行的。遍历提供者、以流方式处理提供者,或调用Provider.get()都可能加载并初始化提供者,因此格式错误的配置、缺失的依赖、不可访问的类型或构造函数失败会以ServiceConfigurationError的形式暴露。应将提供者代码视为部署时提供的代码,在容器或插件系统中选择显式的类加载器,并在受控的启动边界处带诊断信息地失败。

模块服务 #

使用 JPMS 时,使用者模块声明uses com.example.Codec;提供者声明provides com.example.Codec with com.example.JsonCodec。模块解析会按照模块系统规则考虑服务提供者。提供者类不必仅为了ServiceLoader而导出;命名模块中的提供者可以具有 public 无参构造函数或 public static 提供者方法,但必须遵守服务声明的约束。ServiceLoader.load(service, loader)遵循所提供加载器的可见性,而面向模块的重载方法则从模块层运行。加载器和层是服务边界的一部分,而不是无关紧要的实现细节。

类路径配置仍受 unnamed module 和 automatic module 支持,因此可以进行迁移。不要在未测试已解析的提供者集合的情况下同时发布两种机制,因为重复项和对顺序的假设会使选择结果不可移植。

JDBC 作为演进示例 #

早期使用 JDBC 驱动程序时,通常会调用Class.forName("vendor.Driver"),以触发驱动程序向DriverManager进行 static 注册。JDBC 4 引入了针对java.sql.Driver的服务提供者加载机制,使兼容的驱动程序 JAR 可以从META-INF/services/java.sql.Driver中被发现。现代应用仍应显式配置目标驱动程序和连接属性;自动发现省去的是引导调用,而不是数据库身份验证或版本兼容性方面的考虑。

参考资料 #

评论

在 GitHub 上阅读或评论(发表评论需要登录)