Module java.base
Package java.util

Class ServiceLoader<S>

java.lang.Object
java.util.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服务的服务加载器,然后使用其迭代器(由增强for循环自动生成)产生定位到的服务提供者的实例:


     ServiceLoader<CodecFactory> loader = ServiceLoader.load(CodecFactory.class);
     for (CodecFactory factory : loader) {
         Encoder enc = factory.getEncoder("PNG");
         if (enc != null)
             ... 使用enc对PNG文件进行编码
             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()                                              // 注意a
            .filter(p -> p.type().isAnnotationPresent(PNG.class))  // 注意b
            .map(Provider::get)                                    // 注意c
            .collect(Collectors.toSet());
 
  1. Provider<CodecFactory>对象的流
  2. p.type()产生一个Class<CodecFactory>
  3. get()产生一个CodecFactory的实例

设计服务

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

  1. 一个服务应声明尽可能多的方法,以允许服务提供者传达其特定于领域的属性和其他实现质量因素。获取服务的应用程序可以在每个服务提供者的实例上调用这些方法,以选择最适合应用程序的提供者。

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

开发服务提供者

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

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

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

将服务提供者部署为模块

在模块中开发的服务提供者必须在模块声明中指定一个provides指令。provides指令同时指定服务和服务提供者;这有助于在另一个具有对服务使用指令的模块获取服务加载器的服务时定位提供者。强烈建议模块不导出包含服务提供者的包。不支持模块在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中放置一个provider-configuration file来标识。提供者配置文件的名称是服务的完全限定二进制名称。提供者配置文件包含一个服务提供者的完全限定二进制名称列表,每行一个。

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

META-INF/services/com.example.CodecFactory
其中包含以下行:
com.example.impl.StandardCodecs # 标准编解码器

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

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

提供者发现的时机

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

错误

使用服务加载器的iterator时,hasNextnext方法在定位、加载或实例化服务提供者时发生错误时将抛出ServiceConfigurationError。在处理服务加载器的流时,如果导致定位或加载服务提供者的任何方法抛出ServiceConfigurationError

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

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

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

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

安全性

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

并发性

此类的实例不适合多个并发线程使用。

空值处理

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

自 JDK 版本:
1.6
  • Method Details

    • iterator

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

      为了实现延迟加载,定位和实例化提供者的实际工作由迭代器本身完成。因此,其 hasNextnext 方法可能会因为上述 ServiceConfigurationError 中指定的任何原因而抛出错误。编写健壮的代码只需要在使用迭代器时捕获 ServiceConfigurationError。如果抛出错误,则随后调用迭代器将尽力定位和实例化下一个可用提供者,但一般情况下无法保证此类恢复。

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

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

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

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

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

      缓存:在处理流时,之前由流操作加载的提供者首先被处理,按加载顺序排列。然后,它会延迟加载任何剩余的服务提供者。如果通过调用 reload 方法清除了此加载程序的提供者缓存,则应丢弃对此服务加载程序的现有流。返回的流源 spliteratorfail-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);
       
      返回:
      一个流,用于延迟加载此加载程序服务的提供者
      自 JDK 版本:
      9
    • load

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

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

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

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

        排序:服务加载程序将首先定位类加载器定义的模块中的任何服务提供者,然后是其父类加载器、其父类加载器的父类加载器,依此类推直至引导类加载器。如果类加载器在模块层中有模块,则会先定位该模块层中的所有提供者(不考虑其类加载器),然后再定位父类加载器中的提供者。同一类加载器中的模块的顺序,或者模块层中模块的顺序,未定义。

        如果一个模块声明了多个提供者,则提供者的定位顺序与其模块描述符 列出提供者 的顺序相同。由仪器代理动态添加的提供者(参见 redefineModule)总是在模块声明的提供者之后定位。

      • 步骤 2:在未命名模块中定位提供者。

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

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

        在提供者配置文件中,忽略部署在命名模块中的服务提供者的任何提及。这是为了避免当命名模块既有一个 provides 指令又有一个提及相同服务提供者的提供者配置文件时可能产生的重复。

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

      API 注释:
      如果类加载器的类路径包含远程网络URL,则在搜索提供程序配置文件的过程中可能会对这些URL进行解引用。

      这种活动是正常的,尽管可能会导致在Web服务器日志中创建令人困惑的条目。但是,如果Web服务器未正确配置,则此活动可能导致提供程序加载算法出现虚假故障。

      当请求的资源不存在时,Web服务器应返回HTTP 404(未找到)响应。然而,有时Web服务器错误地配置为在这种情况下返回带有有用的HTML错误页面的HTTP 200(OK)响应。这将导致当此类尝试将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())
       

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

      public static <S> ServiceLoader<S> load(ModuleLayer layer, Class<S> service)
      为给定的服务类型创建一个新的服务加载器,以从给定模块层及其祖先的模块中加载服务提供程序。它不会定位未命名模块中的提供程序。服务加载器的iteratorstream定位提供程序和生成元素的顺序如下:
      • 在定位父层提供程序之前,将在模块层中定位提供程序。对父层的遍历是深度优先的,每个层最多访问一次。例如,假设L0是引导层,L1和L2是具有L0作为父级的模块层。现在假设使用L1和L2作为父级(按顺序)创建了L3。使用服务加载器以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()
      清除此加载器的提供程序缓存,以便重新加载所有提供程序。 iteratorstream方法将从头开始懒洋洋地定位提供程序(在 iterator的情况下实例化),就像新创建的服务加载器一样。

    • toString

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