Module  java.base
软件包  java.util

Class ServiceLoader<S>

  • 参数类型
    S - 此装载机要加载的服务类型
    All Implemented Interfaces:
    Iterable<S>


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

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

    获取服务装载程序

    一个应用程序通过调用的静态一个获得用于给定服务的服务加载器load的ServiceLoader的方法。 如果应用程序是模块,则其模块声明必须具有指定该服务的uses指令; 这有助于定位提供商并确保它们能够可靠地执行。 另外,如果服务不在应用程序模块中,则模块声明必须具有指定导出服务的模块的require指令。

    可以使用服务装载器通过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实例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

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

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

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

    将服务提供商部署为模块

    这是一个模块中开发的服务供应商必须指定一个在模块的声明提供了指导。 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并具有公共无参数构造函数的公共类。
    • com.example.impl.ExtendedCodecsFactory是一个不实现CodecFactory的公共类,但它声明一个名为“provider”的public static no-args方法,返回类型为CodecFactory

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

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

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

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

    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文件中。 服务提供者必须从初始查询的类加载器中可见,以查找提供者配置文件; 这不一定是最终找到提供者配置文件的类加载器。

    提供商发现的时间安排

    服务提供商懒惰地加载和实例化,也就是按需。 服务加载器维护到目前为止已经加载的提供程序的缓存。 iterator方法的每次调用iterator返回一个Iterator ,它首先从实例化顺序中获取先前迭代中缓存的所有元素,然后依次放置并实例化任何剩余的提供程序,并将其逐个添加到缓存中。 类似地,流方法的每次调用都返回一个Stream ,它首先处理以先前的流操作加载的所有提供商,并按照加载顺序进行处理,然后懒洋洋地定位任何剩余的提供程序。 缓存通过reload方法清除。

    Errors

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

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

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

    当读取提供者配置文件,或加载或实例化提供者配置文件中命名的提供程序类时,可以抛出ServiceConfigurationError ,原因如下:

    • 提供者配置文件的格式违反上述format ;
    • 读取提供者配置文件时发生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​(Class<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(未找到)响应。 然而,有时,网络服务器错误地配置为在这种情况下返回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)
        使用当前线程的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​(Class<S> service)
        使用platform class loader为给定的服务类型创建一个新的服务加载程序。

        这种方便方法相当于:

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

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

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

        public static <S> ServiceLoader<S> load​(ModuleLayer layer,
                                                Class<S> service)
        为给定的服务类型创建新的服务加载器,以便从给定模块层及其祖先的模块加载服务提供者。 它没有将提供程序定位在未命名的模块中。 服务加载程序的iteratorstream定位提供程序和yield元素的顺序如下:
        • 在父层中定位提供商之前,提供者位于模块层中。 父层的遍历是深度优先的,每层访问最多一次。 例如,假设L0是引导层,L1和L2是以L0为父的模块层。 现在假设L3以L1和L2为父(以该顺序)创建。 使用服务加载程序定位具有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);  
        结果
        第一个服务提供商或空 Optional如果没有服务提供商)
        异常
        ServiceConfigurationError - 如果由于上述 Errors部分中指定的任何原因而无法加载提供者类。
        从以下版本开始:
        9
      • reload

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

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

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

      • toString

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