ServiceLoader源码分析

本文介绍ServiceLoader工具,用于查找和加载服务提供者。服务提供者是实现特定接口或类的类,ServiceLoader允许应用程序在运行时环境中定位这些提供者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

用于加载服务实现的工具。
服务是众所周知的接口或类,其中存在零个,一个或多个服务提供者。 服务提供者 (或只是提供者 )是一个实现或子类知名接口或类的类。 ServiceLoader是一个对象,用于在应用程序选择时查找并加载在运行时环境中部署的服务提供程序。 应用程序代码仅指服务,而不是服务提供商,并且假定能够区分多个服务提供商以及处理没有服务提供商所在的可能性。

获取服务加载程序
应用程序通过调用ServiceLoader的静态load方法之一来获取给定服务的服务加载程序。 如果应用程序是一个模块,那么它的模块声明必须有一个指定服务的uses指令; 这有助于找到提供者并确保他们可靠地执行。 此外,如果服务不在应用程序模块中,则模块声明必须具有requires指令,该指令指定导出服务的模块。

服务加载器可用于通过iterator方法定位和实例化服务的提供者。 ServiceLoader还定义了stream方法,以获取可以在不实例化的情况下进行检查和过滤的提供者流。

例如,假设该服务是com.example.CodecFactory ,这是一个定义生成编码器和解码器的方法的接口:

package com.example; public interface CodecFactory { Encoder getEncoder(String encodingName); Decoder getDecoder(String encodingName); }
以下代码获取CodecFactory服务的服务加载程序,然后使用其迭代器(由enhanced-for循环自动创建)来生成位于以下位置的服务提供程序的实例:

ServiceLoader loader = ServiceLoader.load(CodecFactory.class); for (CodecFactory factory : loader) { Encoder enc = factory.getEncoder(“PNG”); if (enc != null) … use enc to encode a PNG file break; }
如果此代码驻留在模块中,那么为了引用com.example.CodecFactory接口,模块声明将需要导出接口的模块。 模块声明还将指定使用com.example.CodecFactory :

requires com.example.codec.core; uses com.example.CodecFactory;
有时,应用程序可能希望在实例化之前检查服务提供者,以便确定该服务提供者的实例是否有用。 例如,能够产生“PNG”编码器的CodecFactory的服务提供商可以用@PNG注释。 下面的代码使用的服务加载的stream方法产生的实例Provider对比的迭代器是如何产生的实例CodecFactory :

ServiceLoader loader = ServiceLoader.load(CodecFactory.class); Set pngFactories = loader .stream() // Note a below .filter(p -> p.type().isAnnotationPresent(PNG.class)) // Note b .map(Provider::get) // Note c .collect(Collectors.toSet());
Provider对象的流
p.type()得到Class
get()产生的一个实例CodecFactory
设计服务
服务是单一类型,通常是接口或抽象类。 可以使用具体类,但不建议这样做。 该类型可以具有任何可访问性。 服务的方法是高度特定于域的,因此该API规范无法提供有关其形式或功能的具体建议。 但是,有两个一般准则:

服务应声明所需的许多方法,以允许服务提供者传达其特定于域的属性和其他实施质量因素。 获得服务的服务加载器的应用程序然后可以在服务提供者的每个实例上调用这些方法,以便为应用程序选择最佳提供者。

服务应表明其服务提供者是直接实现服务还是间接机制,如“代理”或“工厂”。 当特定于域的对象实例化相对昂贵时,服务提供者往往是间接机制; 在这种情况下,应该设计服务,以便服务提供商是抽象的,按需创建“真正的”实现。 例如, CodecFactory服务通过其名称表示其服务提供者是编解码器的工厂,而不是编解码器本身,因为生成某些编解码器可能是昂贵或复杂的。

Developing service providers
服务提供者是单一类型,通常是具体类。 允许接口或抽象类,因为它可以声明一个静态提供者方法,稍后讨论。 类型必须是公共的,不能是内部类。

可以在模块中开发服务提供商及其支持代码,然后将该模块部署在应用模块路径上或模块化图像中。 或者,服务提供者及其支持代码可以打包为JAR文件并部署在应用程序类路径上。 在模块中开发服务提供者的优点是可以完全封装提供者以隐藏其实现的所有细节。

获取给定服务的服务加载器的应用程序对于服务的提供者是部署在模块中还是打包为JAR文件无关紧要。 应用程序通过服务加载器的迭代器或服务加载器流中的Provider对象实例化服务提供者,而不了解服务提供者的位置。

将服务提供者部署为模块
必须在模块声明中的provide指令中指定在模块中开发的服务提供者。 provide指令指定服务和服务提供者; 当另一个具有服务的uses指令的模块获得服务的服务加载器时,这有助于找到提供者。 强烈建议模块不导出包含服务提供者的包。 不支持在provide指令中指定另一个模块中的服务提供者的模块。

在模块中开发的服务提供程序无法控制它何时被实例化,因为它发生在应用程序的命令之下,但它确实可以控制它的实例化方式:

如果服务提供者声明了提供者方法,则服务加载器调用该方法以获取服务提供者的实例。 提供者方法是名为“provider”的公共静态方法,没有形式参数和可分配给服务的接口或类的返回类型。
在这种情况下,服务提供者本身不需要可分配给服务的接口或类。

如果服务提供者未声明提供者方法,则通过其提供者构造函数直接实例化服务提供者。 提供者构造函数是一个没有形式参数的公共构造函数。
在这种情况下,服务提供者必须可以分配给服务的接口或类

在应用程序模块路径上部署为automatic module的服务提供程序必须具有提供程序构造函数。 在这种情况下,不支持提供者方法。

例如,假设一个模块指定以下指令:

provides com.example.CodecFactory with com.example.impl.StandardCodecs; provides com.example.CodecFactory with com.example.impl.ExtendedCodecsFactory;
哪里

com.example.CodecFactory是早期的双方法服务。
com.example.impl.StandardCodecs是一个实现CodecFactory并具有公共no-args构造函数的公共类。
com.example.impl.ExtendedCodecsFactory是一个不实现CodecFactory的公共类,但它声明了一个名为“provider”的公共静态no-args方法,返回类型为CodecFactory 。
服务加载器将通过其构造函数实例化StandardCodecs ,并将通过调用其provider方法实例化ExtendedCodecsFactory 。 提供者构造函数或提供者方法是公共的要求有助于记录类(即服务提供者)将由类的包外部的实体(即服务加载器)实例化的意图。

在类路径上部署服务提供者
通过将提供程序配置文件放在资源目录META-INF/services来标识打包为类路径的JAR文件的服务提供程序。 provider-configuration文件的名称是服务的完全限定二进制名称。 provider-configuration文件包含服务提供者的完全限定二进制名称列表,每行一个。
例如,假设服务提供者com.example.impl.StandardCodecs打包在类路径的JAR文件中。 JAR文件将包含名为的provider-configuration文件:

META-INF/services/com.example.CodecFactory
包含行:
com.example.impl.StandardCodecs # Standard codecs
The provider-configuration file must be encoded in UTF-8.忽略每个服务提供商名称周围的空格和制表符以及空行。 评论字符是’#’ ( ‘\u0023’ NUMBER SIGN ); 在每一行上,忽略第一个注释字符后面的所有字符。 如果在提供程序配置文件中多次列出服务提供程序类名,则忽略该副本。 如果在多个配置文件中命名服务提供程序类,则忽略该副本。

提供程序配置文件中提到的服务提供程序可以位于与提供程序配置文件相同的JAR文件中,也可以位于不同的JAR文件中。 必须从最初查询的类加载器中看到服务提供者,以找到provider-configuration文件; 这不一定是最终定位提供者配置文件的类加载器。

提供者发现的时间
服务提供程序被懒惰地加载和实例化,即按需。 服务加载程序维护到目前为止已加载的提供程序的缓存。 每次调用iterator方法都会返回一个Iterator ,它首先以实例化的顺序生成从上一次迭代缓存的所有元素,然后懒惰地定位并实例化任何剩余的提供者,依次将每个提供者添加到缓存中。 类似地,每次调用stream方法都会返回一个Stream ,它首先按加载顺序处理先前流操作加载的所有提供程序,然后懒惰地查找任何剩余的提供程序。 通过reload方法清除高速缓存。

Errors
使用服务加载程序iterator ,如果定位,加载或实例化服务提供程序时发生错误,则hasNext和next方法将失败并显示ServiceConfigurationError 。 处理服务加载器的流时,任何导致定位或加载服务提供者的方法ServiceConfigurationError可能抛出ServiceConfigurationError 。

在模块中加载或实例化服务提供程序时,可能会抛出ServiceConfigurationError ,原因如下:

无法加载服务提供商。
服务提供者不声明提供者方法,也不能将其分配给服务的接口/类,或者没有提供者构造函数。
服务提供者声明一个名为“provider”的公共静态no-args方法,其返回类型不能分配给服务的接口或类。
服务提供者类文件有多个名为“ provider ”的公共静态no-args方法。
服务提供者声明了一个提供者方法,它通过返回null或抛出异常而失败。
服务提供者不声明提供者方法,并且其提供者构造函数通过抛出异常而失败。
在读取提供程序配置文件或加载或实例化提供程序配置文件中指定的提供程序类时,可能会抛出ServiceConfigurationError ,原因如下:

provider-configuration文件的格式违反了上面指定的format ;
读取provider-configuration文件时发生IOException ;
无法加载服务提供商;
服务提供者不能分配给服务的接口或类,或者不定义提供者构造函数,或者不能实例化。
安全
服务加载器总是在迭代器或流方法的调用者的安全上下文中执行,也可能受创建服务加载器的调用者的安全上下文的限制。 受信任的系统代码通常应该从特权安全上下文中调用此类中的方法以及它们返回的迭代器的方法。

并发
多个并发线程使用此类的实例是不安全的。

空处理
除非另行指定,否则将null参数传递给null中的任何方法都将导致抛出NullPointerException 。

从以下版本开始:
1.6

1 ServiceLoader

1.1 类图

在这里插入图片描述

1.2 属性

在这里插入图片描述

    // The class or interface representing the service being loaded
    private final Class<S> service;

    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;

    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;

    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;

1.3 实例化

在这里插入图片描述

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }
    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }
    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }
    
    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
    // extClassLoader
    public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        ClassLoader prev = null;
        while (cl != null) {
            prev = cl;
            cl = cl.getParent();
        }
        return ServiceLoader.load(service, prev);
    }

1.4 方法

在这里插入图片描述

2. LazyIterator

2.1 类图

在这里插入图片描述

2.2 属性

在这里插入图片描述

        // The class or interface representing the service being loaded
        Class<S> service;
        // The class loader used to locate, load, and instantiate providers
        ClassLoader loader;
        // configs cache
        Enumeration<URL> configs = null;
        // name cache
        Iterator<String> pending = null;
        // name 哨兵/标志位(hasNextService),暂存(nextService)
        String nextName = null;

2.3 实例化

在这里插入图片描述

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

2.4 方法

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值