模块 java.base
 java.util

类 ServiceLoader<S>

java.lang.Object
java.util.ServiceLoader<S>
类型参数:
S - 此加载程序要加载的服务类型
所有已实现的接口:
Iterable<S>

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

A 服务是一个众所周知的接口或类,存在零个、一个或多个服务提供者。 A服务提供者(要不就供应商是一个实现或子类化众所周知的接口或类的类。 ServiceLoader 是一个对象,它在应用程序选择时定位并加载部署在运行时环境中的服务提供者。应用程序代码仅涉及服务,不涉及服务提供者,并且假定能够在多个服务提供者之间进行选择(基于它们通过服务公开的功能),并处理找不到服务提供者的可能性。

获取服务加载器

应用程序通过调用 ServiceLoader 的静态 load 方法之一获取给定服务的服务加载器。如果应用程序是一个模块,那么它的模块声明必须有一个使用指定服务的指令;这有助于找到供应商并确保他们可靠地执行。另外,如果应用模块不包含服务,那么它的模块声明必须有一个需要指定导出服务的模块的指令。强烈建议应用模块做not需要包含服务提供者的模块。

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

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


   package com.example;
   public interface CodecFactory {
     Encoder getEncoder(String encodingName);
     Decoder getDecoder(String encodingName);
   }
  

以下代码获取 CodecFactory 服务的服务加载器,然后使用其迭代器(由增强型 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 服务通过其名称表示其服务提供商是编解码器的工厂,而不是编解码器本身,因为生产某些编解码器可能很昂贵或很复杂。

发展服务供应商

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

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

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

将服务提供者部署为模块

在模块中开发的服务提供者必须在提供模块声明中的指令。 provides 指令指定了服务和服务提供者;这有助于在另一个模块使用使用服务指令,获取服务的服务加载器。强烈建议模块不要导出包含服务提供者的包。不支持模块指定,在提供指令,另一个模块中的服务提供者。

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

  • 如果服务提供者声明了提供者方法,则服务加载器调用该方法来获取服务提供者的实例。提供者方法是一个名为“provider”的公共静态方法,没有形式参数和可分配给服务接口或类的返回类型。

    在这种情况下,服务提供者本身不需要分配给服务的接口或类。

  • 如果服务提供者未声明提供者方法,则服务提供者将通过其提供者构造方法直接实例化。提供者构造方法是没有形式参数的公共构造方法。

    在这种情况下,服务提供者必须可分配给服务的接口或类

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

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


   provides com.example.CodecFactory with com.example.impl.StandardCodecs,
       com.example.impl.ExtendedCodecsFactory;
  

在哪里

  • com.example.CodecFactory 是之前的两种方法服务。
  • com.example.impl.StandardCodecs 是实现 CodecFactory 的公共类,并具有公共无参数构造函数。
  • com.example.impl.ExtendedCodecsFactory 是一个未实现 CodecFactory 的公共类,但它声明了一个名为“provider”的公共静态无参数方法,返回类型为 CodecFactory

服务加载器将通过其构造函数实例化StandardCodecs,并通过调用其provider方法实例化ExtendedCodecsFactory。提供者构造函数或提供者方法是公共的这一要求有助于记录类(即服务提供者)将由类包外的实体(即服务加载器)实例化的意图。

在类路径上部署服务提供者

为类路径打包为 JAR 文件的服务提供者通过放置供应商配置文件在资源目录 META-INF/services 中。提供程序配置文件的名称是服务的完全限定二进制名称。提供者配置文件包含服务提供者的完全限定二进制名称列表,每行一个。

例如,假设服务提供者 com.example.impl.StandardCodecs 打包在类路径的 JAR 文件中。 JAR 文件将包含一个名为:

META-INF/services/com.example.CodecFactory
包含以下行:
com.example.impl.StandardCodecs # Standard codecs

提供程序配置文件必须以 UTF-8 编码。 每个服务提供商名称周围的空格和制表符以及空行都将被忽略。注释字符为'#' (U+0023NUMBER SIGN);在每一行中,第一个注释字符之后的所有字符都将被忽略。如果一个服务提供者类名在一个提供者配置文件中被多次列出,那么重复的将被忽略。如果一个服务提供者类在多个配置文件中被命名,那么重复的将被忽略。

提供者配置文件中提及的服务提供者可能位于与提供者配置文件相同的 JAR 文件中,也可能位于不同的 JAR 文件中。服务提供者必须从类加载器中可见,类加载器最初被查询以定位提供者配置文件;这不一定是最终定位提供者配置文件的类加载器。

提供商发现的时机

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

错误

使用服务加载器的 iterator 时,如果在定位、加载或实例化服务提供者时发生错误,则 hasNext next 方法将失败并返回 ServiceConfigurationError 。当处理服务加载器的流时,ServiceConfigurationError 可能会被任何导致服务提供者被定位或加载的方法抛出。

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

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

当读取提供者配置文件,或加载或实例化在提供者配置文件中命名的提供者类时,由于以下原因可能会抛出 ServiceConfigurationError

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

Security

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

并发

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

空值处理

除非另有说明,否则将 null 参数传递给此类中的任何方法都将导致抛出 NullPointerException

自从:
1.6
  • 方法详情

    • iterator

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

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

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

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

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

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

      为了实现惰性,定位提供者的实际工作是在处理流时完成的。如果由于上面 错误 部分中指定的任何原因无法加载服务提供者,则 ServiceConfigurationError 将被导致加载服务提供者的任何方法抛出。

      缓存:在处理流时,首先按加载顺序处理之前由流操作加载的提供程序。然后它延迟加载任何剩余的服务提供者。如果通过调用 reload 方法清除此加载程序的提供程序缓存,则应丢弃此服务加载程序的现有流。返回的流的源 spliterator fail-fast,如果提供程序缓存已被清除,将抛出 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(Class <S> service, ClassLoader  loader)
      为给定的服务创建一个新的服务加载器。服务加载器使用给定的类加载器作为起点来为服务定位服务提供者。服务加载器的 iterator stream 在命名和未命名模块中定位提供者,如下所示:
      • 第 1 步:在命名模块中找到提供者。

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

        此外,如果类加载器不是引导程序或 平台类加载器 ,则服务提供者可能位于其他类加载器的命名模块中。具体来说,如果类加载器或任何可通过父委派访问的类加载器在 模块层 中有一个模块,则定位到模块层中所有模块中的服务提供者。

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

        排序:服务加载器将首先定位定义给类加载器的模块中的任何服务提供者,然后是它的父类加载器,它的父类加载器,等等到引导类加载器。如果类加载器在模块层中有模块,则该模块层中的所有提供者(无论其类加载器如何)都在父类加载器中的提供者定位之前定位。未定义同一类加载器中模块的顺序,或模块层中模块的顺序。

        如果一个模块声明了多个提供者,那么这些提供者将按照其模块描述符 列出提供者 的顺序排列。由检测代理动态添加的提供程序(请参阅 redefineModule )始终位于模块声明的提供程序之后。

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

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

        排序基于类加载器的 getResources 方法找到服务配置文件的顺序,以及类名称在文件中列出的顺序。

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

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

      API 注意:
      如果类加载器的类路径包含远程网络 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(Class <S> service)
      使用当前线程的 上下文类加载器 为给定的服务类型创建一个新的服务加载器。

      调用此表单的便捷方法

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

      public static <S> ServiceLoader <S> loadInstalled(Class <S> service)
      使用 平台类加载器 为给定的服务类型创建一个新的服务加载器。

      这个方便的方法等同于:

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

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

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

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

      • 如果一个模块声明了多个提供者,那么这些提供者将按照其模块描述符 列出提供者 的顺序排列。检测代理动态添加的提供者总是位于模块声明的提供者之后。

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

      API 注意:
      与此处定义的其他加载方法不同,服务类型是第二个参数。这样做的原因是为了避免使用 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);
        
      返回:
      第一个服务提供者或空 Optional 如果没有找到服务提供者
      抛出:
      ServiceConfigurationError - 如果由于上述 错误 部分中指定的任何原因而无法加载提供程序类。
      自从:
      9
    • reload

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

      调用此方法后,后续调用 iterator stream 方法将从头开始懒惰地定位提供者(并在 iterator 的情况下实例化),就像新创建的服务加载器所做的那样。

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

    • toString

      public String  toString()
      返回描述此服务的字符串。
      重写:
      toString 在类 Object
      返回:
      描述性字符串