模块  java.base
软件包  java.util

Class ServiceLoader<S>

  • 参数类型
    S - 此加载程序要加载的服务的类型
    实现的所有接口
    Iterable<S>

    public final class ServiceLoader<S>
    extends Object
    implements Iterable<S>
    用于加载服务实现的工具。

    服务是众所周知的接口或类,其中存在零个,一个或多个服务提供者。 服务提供者 (或只是提供者 )是一个实现或子类知名接口或类的类。 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<CodecFactory> 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>对比的迭代器是如何产生的实例CodecFactory

       ServiceLoader<CodecFactory> loader = ServiceLoader.load(CodecFactory.class); Set<CodecFactory> pngFactories = loader .stream() // Note a below .filter(p -> p.type().isAnnotationPresent(PNG.class)) // Note b .map(Provider::get) // Note c .collect(Collectors.toSet());  
    1. Provider<CodecFactory>对象的流
    2. p.type()得到Class<CodecFactory>
    3. get()产生的一个实例CodecFactory

    设计服务

    服务是单一类型,通常是接口或抽象类。 可以使用具体类,但不建议这样做。 该类型可以具有任何可访问性。 服务的方法是高度特定于域的,因此该API规范无法提供有关其形式或功能的具体建议。 但是,有两个一般准则:

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

    2. 服务应表明其服务提供者是直接实现服务还是间接机制,如“代理”或“工厂”。 当特定于域的对象实例化相对昂贵时,服务提供者往往是间接机制; 在这种情况下,应该设计服务,以便服务提供商是抽象的,按需创建“真正的”实现。 例如, 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.忽略每个服务提供商名称周围的空格和制表符以及空行。 评论字符是'#''&#92;u0023' NUMBER SIGN ); 在每一行上,忽略第一个注释字符后面的所有字符。 如果在提供程序配置文件中多次列出服务提供程序类名,则忽略该副本。 如果在多个配置文件中命名服务提供程序类,则忽略该副本。

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

    提供者发现的时间

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

    Errors

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

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

    • 无法加载服务提供商。
    • 服务提供者不声明提供者方法,也不能将其分配给服务的接口/类,或者没有提供者构造函数。
    • 服务提供者声明一个名为“provider”的公共静态no-args方法,其返回类型不能分配给服务的接口或类。
    • 服务提供者类文件有多个名为“ provider ”的公共静态no-args方法。
    • 服务提供者声明了一个提供者方法,它通过返回null或抛出异常而失败。
    • 服务提供者不声明提供者方法,并且其提供者构造函数通过抛出异常而失败。

    在读取提供程序配置文件或加载或实例化提供程序配置文件中指定的提供程序类时,可能会抛出ServiceConfigurationError ,原因如下:

    • provider-configuration文件的格式违反了上面指定的format ;
    • 读取provider-configuration文件时发生IOException ;
    • 无法加载服务提供商;
    • 服务提供者不能分配给服务的接口或类,或者不定义提供者构造函数,或者不能实例化。

    安全

    服务加载器总是在迭代器或流方法的调用者的安全上下文中执行,也可能受创建服务加载器的调用者的安全上下文的限制。 受信任的系统代码通常应该从特权安全上下文中调用此类中的方法以及它们返回的迭代器的方法。

    并发

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

    空处理

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

    从以下版本开始:
    1.6
    • 方法详细信息

      • iterator

        public Iterator<S> iterator()
        返回一个延迟加载的迭代器,并实例化此加载器服务的可用提供程序。

        为了实现惰性,定位和实例化提供者的实际工作是由迭代器本身完成的。 因此, hasNextnext方法可以出于上述Errors部分中指定的任何原因抛出ServiceConfigurationError 要编写健壮的代码,只需要在使用迭代器时捕获ServiceConfigurationError 如果抛出错误,则迭代器的后续调用将尽最大努力定位并实例化下一个可用的提供程序,但通常无法保证此类恢复。

        缓存:此方法返回的迭代器首先按照加载的顺序生成提供程序缓存的所有元素。 然后它懒洋洋地加载并实例化任何剩余的服务提供者,依次将每个提供者添加到缓存中。 如果通过调用reload方法清除此加载程序的提供程序缓存,则应丢弃此服务加载程序的现有迭代器。 如果在清除提供程序缓存后使用,则迭代器的hasNextnext方法将抛出ConcurrentModificationException

        此方法返回的迭代器不支持删除。 调用其remove方法将导致抛出UnsupportedOperationException

        Specified by:
        iterator接口 Iterable<S>
        API Note:
        在这些情况下抛出错误可能看起来很极端。 此行为的基本原理是格式错误的提供程序配置文件(如格式错误的类文件)表明Java虚拟机的配置或使用方式存在严重问题。 因此,最好抛出错误而不是尝试恢复,或者更糟糕的是,无声地失败。
        结果
        延迟加载此加载器服务的提供程序的迭代器
      • stream

        public Stream<ServiceLoader.Provider<S>> stream()
        返回一个流,以延迟加载此加载程序服务的可用提供程序。 流元素的类型为Provider ,必须调用Providerget方法来获取或实例化提供程序。

        为了实现懒惰,在处理流时完成定位提供者的实际工作。 如果由于上述Errors部分中指定的任何原因而无法加载服务提供者,则无论采用何种方法导致加载服务提供者,都会引发ServiceConfigurationError

        缓存:处理流时,首先按加载顺序处理先前由流操作加载的提供程序。 然后它懒洋洋地加载任何剩余的服务提供商。 如果通过调用reload方法清除此加载程序的提供程序缓存,则应丢弃此服务加载程序的现有流。 返回的流的源spliterator快速失败的 ,如果已清除提供程序缓存,则将抛出ConcurrentModificationException

        以下示例演示了用法。 第一个示例创建了一个CodecFactory对象的流,第二个示例是相同的,只是它按提供程序类名对提供程序进行排序(因此找到所有提供程序)。

           Stream<CodecFactory> providers = ServiceLoader.load(CodecFactory.class) .stream() .map(Provider::get); Stream<CodecFactory> providers = ServiceLoader.load(CodecFactory.class) .stream() .sorted(Comparator.comparing(p -> p.type().getName())) .map(Provider::get);  
        结果
        延迟加载此加载程序服务的提供程序的流
        从以下版本开始:
        9
      • load

        public static <S> ServiceLoader<S> load​(<S> service,
                                                ClassLoader loader)
        为给定服务创建新的服务加载器。 服务加载器使用给定的类加载器作为定位服务的服务提供者的起点。 服务加载程序的iteratorstream定位了命名和未命名模块中的提供程序,如下所示:
        • 第1步:在命名模块中找到提供程序。

          服务提供程序位于类加载器的所有命名模块中,或者位于可通过父代理访问的任何类加载器中。

          此外,如果类加载器不是引导程序或platform class loader ,则服务提供程序可能位于其他类加载器的命名模块中。 具体来说,如果类加载器或通过父代理可访问的任何类加载器在module layer中具有模块,则位于模块层中所有模块中的服务提供者。

          例如,假设有一个模块层,其中每个模块都在自己的类加载器中(参见defineModulesWithManyLoaders )。 如果调用此ServiceLoader.load方法以使用为模块层创建的任何类加载器来定位提供程序,则它将定位模块层中的所有提供程序,而不管它们是否定义了类加载器。

          排序:服务加载器将首先在定义到类加载器的模块中定位任何服务提供者,然后定位其父类加载器,其父父类,依此类推到引导类加载器。 如果类加载器在模块层中具有模块,那么在父类加载器中的提供者所在之前,该模块层中的所有提供者都被定位(无论其类加载器如何)。 未定义同一类加载器中的模块的排序,或模块层中的模块的排序。

          如果模块声明多于一个提供者,则提供者按其模块描述符lists the providers的顺序定位 由检测代理动态添加的提供程序(请参阅redefineModule )始终位于模块声明的提供程序之后。

        • 第2步:在未命名的模块中找到提供程序。

          如果类名称列在由类加载器的getResources方法定位的提供程序配置文件中,则定位未命名模块中的服务提供程序。

          排序基于类加载器的getResources方法查找服务配置文件的顺序,其中包括类名在文件中列出的顺序。

          在提供程序配置文件中,将忽略部署在命名模块中的任何提及的服务提供程序。 这是为了避免在命名模块同时具有提供指令和提及相同服务提供者的提供者配置文件时可能出现的重复。

          提供程序类必须对类加载器可见。

        API Note:
        如果类加载器的类路径包括远程网络URL,则在搜索提供者配置文件的过程中可以取消引用这些URL。

        此活动是正常的,但可能会导致在Web服务器日志中创建令人费解的条目。 但是,如果未正确配置Web服务器,则此活动可能导致提供程序加载算法虚假失败。

        当请求的资源不存在时,Web服务器应返回HTTP 404(未找到)响应。 但是,有时,Web服务器被错误地配置为在这种情况下返回HTTP 200(OK)响应以及有用的HTML错误页面。 当此类尝试将HTML页面解析为提供程序配置文件时,这将导致抛出ServiceConfigurationError 此问题的最佳解决方案是修复配置错误的Web服务器以返回正确的响应代码(HTTP 404)以及HTML错误页面。

        参数类型
        S - 服务类型的类
        参数
        service - 表示服务的接口或抽象类
        loader - 用于加载提供程序配置文件和提供程序类的类加载程序,或者如果要使用系统类加载程序(或者,失败,则使用引导类加载程序), null
        结果
        一个新的服务加载器
        异常
        ServiceConfigurationError - 如果调用者无法访问服务类型,或者调用者位于显式模块中,并且其模块描述符未声明它使用 service
      • load

        public static <S> ServiceLoader<S> load​(<S> service)
        使用当前线程的context class loader为给定的服务类型创建新的服务加载器。

        调用表单的这种方便方法

           ServiceLoader.load(service)  
        相当于
           ServiceLoader.load(service, Thread.currentThread().getContextClassLoader())  
        API Note:
        使用此方法获取的服务加载程序对象不应在VM范围内进行缓存。 例如,同一VM中的不同应用程序可能具有不同的线程上下文类加载器。 一个应用程序的查找可以定位仅通过其线程上下文类加载器可见的服务提供者,因此不适合由其他应用程序定位。 内存泄漏也可能出现。 本地线程可能适合某些应用程序。
        参数类型
        S - 服务类型的类
        参数
        service - 表示服务的接口或抽象类
        结果
        一个新的服务加载器
        异常
        ServiceConfigurationError - 如果调用者无法访问服务类型,或者调用者位于显式模块中,并且其模块描述符未声明它使用 service
      • loadInstalled

        public static <S> ServiceLoader<S> loadInstalled​(<S> service)
        使用platform class loader为给定的服务类型创建新的服务加载器。

        这种便捷方法相当于:

           ServiceLoader.load(service, ClassLoader.getPlatformClassLoader())  

        此方法仅在需要安装的提供程序时使用。 生成的服务只会查找并加载已安装到当前Java虚拟机中的提供程序; 应用程序的模块路径或类路径上的提供程序将被忽略。

        参数类型
        S - 服务类型的类
        参数
        service - 表示服务的接口或抽象类
        结果
        一个新的服务加载器
        异常
        ServiceConfigurationError - 如果调用者无法访问服务类型,或者调用者位于显式模块中,并且其模块描述符未声明它使用 service
      • load

        public static <S> ServiceLoader<S> load​(ModuleLayer layer,
                                                <S> service)
        为给定的服务类型创建新的服务加载器,以从给定模块层及其祖先的模块加载服务提供者。 它没有在未命名的模块中找到提供程序。 服务加载程序iteratorstream定位提供程序和yield元素的顺序如下:
        • 在父层中定位提供程序之前,提供程序位于模块层中。 父层的遍历是深度优先的,每层最多访问一次。 例如,假设L0是引导层,L1和L2是模块层,L0作为其父级。 现在假设使用L1和L2作为父项创建L3(按此顺序)。 使用服务加载程序定位具有L3的提供程序作为上下文将按以下顺序定位提供程序:L3,L1,L0,L2。

        • 如果模块声明多个提供者,则提供者按其模块描述符lists the providers的顺序定位 由检测代理动态添加的提供程序始终位于模块声明的提供程序之后。

        • 未定义模块层中模块的顺序。

        API Note:
        与此处定义的其他加载方法不同,服务类型是第二个参数。 这样做的原因是为了避免使用load(S, null)代码的源兼容性问题。
        参数类型
        S - 服务类型的类
        参数
        layer - 模块层
        service - 表示服务的接口或抽象类
        结果
        一个新的服务加载器
        异常
        ServiceConfigurationError - 如果调用者无法访问服务类型,或者调用者位于显式模块中,并且其模块描述符未声明它使用 service
        从以下版本开始:
        9
      • findFirst

        public Optional<S> findFirst()
        加载此加载程序服务的第一个可用服务提供程序。 这种方便方法相当于调用iterator()方法并获得第一个元素。 因此,如果可能,它会从提供程序缓存中返回第一个元素,否则它会尝试加载并实例化第一个提供程序。

        以下示例加载第一个可用的服务提供者。 如果找不到服务提供者,则它使用默认实现。

           CodecFactory factory = ServiceLoader.load(CodecFactory.class) .findFirst() .orElse(DEFAULT_CODECSET_FACTORY);  
        结果
        第一个服务提供商,如果没有服务提供商, OptionalOptional
        异常
        ServiceConfigurationError - 如果由于上述 Errors部分中指定的任何原因而无法加载提供程序类。
        从以下版本开始:
        9
      • reload

        public void reload()
        清除此加载程序的提供程序缓存,以便重新加载所有提供程序。

        在调用此方法之后, iteratorstream方法的后续调用将从头开始懒洋洋地定位提供程序(并在iterator的情况下实例化),就像新创建的服务加载程序一样。

        此方法适用于可以将新服务提供程序安装到正在运行的Java虚拟机中的情况。

      • toString

        public String toString()
        返回描述此服务的字符串。
        重写:
        toString在类 Object
        结果
        A descriptive string