# 创建可扩展的应用程序

> 原文： [https://docs.oracle.com/javase/tutorial/ext/basics/spi.html](https://docs.oracle.com/javase/tutorial/ext/basics/spi.html)

涵盖以下主题：

*   [简介](#introduction)
*   [字典服务示例](#dictionary-service-example)
*   [运行 DictionaryServiceDemo 示例](#running-the-dictionaryservicedemo-sample)
*   [编译并运行 DictionaryServiceDemo 示例](#compiling-and-running-the-dictionaryservicedemo-sample)
*   [了解 DictionaryServiceDemo 示例](#understanding-the-dictionaryservicedemo-sample)
    1.  [定义服务提供商接口](#define-the-service-provider-interface)
    2.  [定义检索服务提供者实现的服务](#definte-the-service-that-retrieves-service-provider-implementations)
        *   [单例设计模式](#singleton)
    3.  [实现服务提供商](#implement-the-service-provider)
    4.  [注册服务提供商](#register-service-providers)
    5.  [创建使用服务和服务提供商的客户端](#create-a-client-that-uses-the-service-and-service-providers)
    6.  [将服务提供商，服务和服务客户​​端打包在 JAR 文件](#package-in-jar-files)中
        *   [JAR 文件中的打包服务提供商](#packaging-service-providers-in-jar-files)
        *   [在 JAR 文件中打包字典 SPI 和字典服务](#packaging-the-dictionary-service-in-a-jar-file)
        *   [将客户端打包在 JAR 文件中](#packaging-the-client-in-a-jar-file)
    7.  [运行客户端](#run-the-client)
*   [ServiceLoader 类](#the-serviceloader-class)
*   [ServiceLoader API](#limitations-of-the-service-loader-api) 的限制
*   [摘要](#summary)

_ 可扩展 _ 应用程序是您可以在不修改其原始代码库的情况下扩展的应用程序。您可以使用新的插件或模块增强其功能。开发人员，软件供应商和客户可以通过将新的 Java Archive（JAR）文件添加到应用程序类路径或特定于应用程序的扩展目录中来添加新功能或应用程序编程接口（API）。

本节介绍如何使用可扩展服务创建应用程序，使您或其他人能够提供不需要修改原始应用程序的服务实现。通过设计可扩展的应用程序，您可以在不更改核心应用程序的情况下提供升级或增强产品特定部分的方法。

可扩展应用程序的一个示例是允许最终用户添加新词典或拼写检查器的文字处理器。在此示例中，文字处理器提供字典或拼写功能，其他开发人员甚至客户可以通过提供他们自己的功能实现来扩展。

以下是了解可扩展应用程序的重要术语和定义：

Service

A set of programming interfaces and classes that provide access to some specific application functionality or feature. The service can define the interfaces for the functionality and a way to retrieve an implementation. In the word-processor example, a dictionary service can define a way to retrieve a dictionary and the definition of a word, but it does not implement the underlying feature set. Instead, it relies on a _service provider_ to implement that functionality.

Service provider interface (SPI)

The set of public interfaces and abstract classes that a service defines. The SPI defines the classes and methods available to your application.

Service Provider

Implements the SPI. An application with extensible services enable you, vendors, and customers to add service providers without modifying the original application.

考虑如何在字处理器或编辑器中设计字典服务。一种方法是定义由名为`DictionaryService`的类和名为`Dictionary`的服务提供者接口表示的服务。 `DictionaryService`提供单个`DictionaryService`对象。 （有关详细信息，请参阅[单例设计模式](#singleton)部分。）此对象从`Dictionary`提供程序中检索单词的定义。字典服务客户端 - 您的应用程序代码 - 检索此服务的实例，该服务将搜索，实例化和使用`Dictionary`服务提供程序。

虽然文字处理器开发人员很可能会提供原始产品的基本通用字典，但客户可能需要专门的字典，可能包含法律或技术术语。理想情况下，客户可以创建或购买新词典并将其添加到现有应用程序中。

`[DictionaryServiceDemo](examples/DictionaryServiceDemo/DictionaryServiceDemo.zip) `示例向您展示如何实现`Dictionary`服务，创建添加其他词典的`Dictionary`服务提供程序，并创建一个测试服务的简单`Dictionary`服务客户端。此示例包含在 zip 文件 `[DictionaryServiceDemo.zip](examples/DictionaryServiceDemo/DictionaryServiceDemo.zip)`中，由以下文件组成：

*   [``build.xml``](examples/DictionaryServiceDemo/build.xml)
*   `DictionaryDemo`
    *   [``build.xml``](examples/DictionaryServiceDemo/DictionaryDemo/build.xml)
    *   `build`
    *   `dist`
        *   `DictionaryDemo.jar`
    *   `src`
        *   `dictionary`
            *   [``DictionaryDemo.java``](examples/DictionaryServiceDemo/DictionaryDemo/src/dictionary/DictionaryDemo.java)
*   `DictionaryServiceProvider`
    *   [``build.xml``](examples/DictionaryServiceDemo/DictionaryDemo/build.xml)
    *   `build`
    *   `dist`
        *   `DictionaryServiceProvider.jar`
    *   `src`
        *   `dictionary`
            *   [``DictionaryService.java``](examples/DictionaryServiceDemo/DictionaryServiceProvider/src/dictionary/DictionaryService.java)
            *   `spi`
                *   [``Dictionary.java``](examples/DictionaryServiceDemo/DictionaryServiceProvider/src/dictionary/spi/Dictionary.java)
*   `ExtendedDictionary`
    *   [``build.xml``](examples/DictionaryServiceDemo/ExtendedDictionary/build.xml)
    *   `build`
    *   `dist`
        *   `ExtendedDictionary.jar`
    *   `src`
        *   `dictionary`
            *   [``ExtendedDictionary.java``](examples/DictionaryServiceDemo/ExtendedDictionary/src/dictionary/ExtendedDictionary.java)
        *   `META-INF`
            *   `services`
                *   [``dictionary.spi.Dictionary``](examples/DictionaryServiceDemo/ExtendedDictionary/src/META-INF/services/dictionary.spi.Dictionary)
*   `GeneralDictionary`
    *   [``build.xml``](examples/DictionaryServiceDemo/GeneralDictionary/build.xml)
    *   `build`
    *   `dist`
        *   `GeneralDictionary.jar`
    *   `src`
        *   `dictionary`
            *   [``GeneralDictionary.java``](examples/DictionaryServiceDemo/GeneralDictionary/src/dictionary/GeneralDictionary.java)
        *   `META-INF`
            *   `services`
                *   [``dictionary.spi.Dictionary``](examples/DictionaryServiceDemo/GeneralDictionary/src/META-INF/services/dictionary.spi.Dictionary)

**注**：`build`目录包含同一级别`src`目录中包含的 Java 源文件的已编译类文件。

因为 zip 文件 `[DictionaryServiceDemo.zip](examples/DictionaryServiceDemo/DictionaryServiceDemo.zip)`包含已编译的类文件，您可以通过以下步骤将此文件解压缩到您的计算机并运行该示例而不编译它：

1.  下载并解压缩示例代码：将文件 `[DictionaryServiceDemo.zip](examples/DictionaryServiceDemo/DictionaryServiceDemo.zip)`下载并解压缩到您的计算机。这些步骤假设您将此文件的内容解压缩到目录`C:\DictionaryServiceDemo`中。

2.  将当前目录更改为`C:\DictionaryServiceDemo\DictionaryDemo`并按照步骤[运行客户端](#run-the-client)。

`DictionaryServiceDemo`示例包含 Apache Ant 构建文件，这些文件都被命名为`build.xml`。以下步骤说明如何使用 Apache Ant 编译，构建和运行`DictionaryServiceDemo`示例：

1.  安装 Apache Ant：转到以下链接下载并安装 Apache Ant：

    `[http://ant.apache.org/](http://ant.apache.org/)`

    确保包含 Apache Ant 可执行文件的目录位于`PATH`环境变量中，以便可以从任何目录运行它。此外，请确保您的 JDK 的`bin`目录，其中包含`java`和`javac`可执行文件（Microsoft Windows 的`java.exe`和`javac.exe`）。在您的`PATH`环境变量中。有关设置`PATH`环境变量的信息，请参见 [PATH 和 CLASSPATH](../../essential/environment/paths.html) 。

2.  下载并解压缩示例代码：将文件 `[DictionaryServiceDemo.zip](examples/DictionaryServiceDemo/DictionaryServiceDemo.zip)`下载并解压缩到您的计算机。这些步骤假定您将此文件的内容解压缩到目录`C:\DictionaryServiceDemo`中。

3.  编译代码：将当前目录更改为`C:\DictionaryServiceDemo`并运行以下命令：

    ```
    ant compile-all
    ```

    此命令编译目录`DictionaryDemo`，`DictionaryServiceProvider`，`ExtendedDictionary`和`GeneralDictionary`中包含的`src`目录中的源代码，并将生成的`class`文件放在相应的`build`目录中。

4.  将已编译的 Java 文件打包到 JAR 文件中：确保当前目录为`C:\DictionaryServiceDemo`并运行以下命令：

    ```
    ant jar
    ```

    此命令将创建以下 JAR 文件：

    *   `DictionaryDemo/dist/DictionaryDemo.jar`
    *   `DictionaryServiceProvider/dist/DictionaryServiceProvider.jar`
    *   `GeneralDictionary/dist/GeneralDictionary.jar`
    *   `ExtendedDictionary/dist/ExtendedDictionary.jar`
5.  运行示例：确保包含`java`可执行文件的目录位于`PATH`环境变量中。有关详细信息，请参阅 [PATH 和 CLASSPATH](../../essential/environment/paths.html) 。

    将当前目录更改为`C:\DictionaryServiceDemo\DictionaryDemo`并运行以下命令：

    ```
    ant run
    ```

    该示例打印以下内容：

    `书：一组书面或打印的页面，通常绑定保护盖
    编辑器：编辑
    xml 的人：Web 服务中经常使用的文档标准，
    REST：用于创建，读取，更新和删除试图使用 HTTP 协议的通用词汇表的数据的体系结构样式;代表性国家转移`

以下步骤说明如何重新创建文件 `[DictionaryServiceDemo.zip](examples/DictionaryServiceDemo/DictionaryServiceDemo.zip)`的内容。这些步骤向您展示了示例的工作原理以及如何运行它。

`DictionaryServiceDemo`样本定义了一个 SPI， [``Dictionary.java``](examples/DictionaryServiceDemo/DictionaryServiceProvider/src/dictionary/spi/Dictionary.java)接口。它只包含一种方法：

```
package dictionary.spi;

public interface Dictionary {
    public String getDefinition(String word);
}

```

该示例将已编译的类文件存储在目录`DictionaryServiceProvider/build`中。

[``DictionaryService.java`` ](examples/DictionaryServiceDemo/DictionaryServiceProvider/src/dictionary/DictionaryService.java)类代表字典服务客户端加载和访问可用的`Dictionary`服务提供者：

```
package dictionary;

import dictionary.spi.Dictionary;
import java.util.Iterator;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;

public class DictionaryService {

    private static DictionaryService service;
    private ServiceLoader<Dictionary> loader;

    private DictionaryService() {
        loader = ServiceLoader.load(Dictionary.class);
    }

    public static synchronized DictionaryService getInstance() {
        if (service == null) {
            service = new DictionaryService();
        }
        return service;
    }

    public String getDefinition(String word) {
        String definition = null;

        try {
            Iterator<Dictionary> dictionaries = loader.iterator();
            while (definition == null && dictionaries.hasNext()) {
                Dictionary d = dictionaries.next();
                definition = d.getDefinition(word);
            }
        } catch (ServiceConfigurationError serviceError) {
            definition = null;
            serviceError.printStackTrace();

        }
        return definition;
    }
}

```

该示例将已编译的类文件存储在目录`DictionaryServiceProvider/build`中。

`DictionaryService`类实现单例设计模式。这意味着只创建了`DictionaryService`类的单个实例。有关更多信息，请参见[单例设计模式](#singleton)部分。

`DictionaryService`类是字典服务客户端使用任何已安装的`Dictionary`服务提供者的入口点。使用 [`ServiceLoader.load`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html#load-java.lang.Class-) 方法检索私有静态成员`DictionaryService.service`，即单例服务入口点。然后应用程序可以调用`getDefinition`方法，该方法迭代可用的`Dictionary`提供程序，直到找到目标字。如果没有`Dictionary`实例包含指定的单词定义，则`getDefinition`方法返回 null。

字典服务使用`ServiceLoader.load`方法来查找目标类。 SPI 由接口`dictionary.spi.Dictionary`定义，因此该示例使用此类作为加载方法的参数。默认的 load 方法使用默认的类加载器搜索应用程序类路径。

但是，如果您愿意，可以使用此方法的重载版本来指定自定义类加载器。这使您可以进行更复杂的类搜索。例如，一个特别热心的程序员可能会创建一个 [`ClassLoader`](https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html) 实例，该实例可以在特定于应用程序的子目录中搜索，该子目录包含在运行时添加的提供程序 JAR。结果是一个不需要重新启动来访问新提供程序类的应用程序。

存在此类的加载器后，您可以使用其迭代器方法来访问和使用它找到的每个提供程序。 `getDefinition`方法使用`Dictionary`迭代器遍历提供程序，直到找到指定单词的定义。迭代器方法缓存`Dictionary`实例，因此连续调用几乎不需要额外的处理时间。如果自上次调用以来已将新提供程序置于服务中，则迭代器方法会将它们添加到列表中。

[``DictionaryDemo.java`` ](examples/DictionaryServiceDemo/DictionaryDemo/src/dictionary/DictionaryDemo.java)类使用此服务。要使用该服务，应用程序将获取`DictionaryService`实例并调用`getDefinition`方法。如果定义可用，应用程序将打印它。如果定义不可用，应用程序将打印一条消息，指出没有可用的字典包含该单词。

设计模式是软件设计中常见问题的通用解决方案。我们的想法是将解决方案转换为代码，并且该代码可以应用于发生问题的不同情况。单例模式描述了一种技术，以确保只创建一个类的单个实例。本质上，该技术采用以下方法：不要让类外的任何人创建对象的实例。

例如， [``DictionaryService``](examples/DictionaryServiceDemo/DictionaryServiceProvider/src/dictionary/DictionaryService.java)类实现单例模式如下：

*   将`DictionaryService`构造函数声明为`private`，它会阻止除`DictionaryService`之外的所有其他类创建它的实例。
*   将`DictionaryService`成员变量`service`定义为`static`，确保只存在`DictionaryService`的一个实例。
*   定义方法`getInstance`，它允许其他类控制访问`DictionaryService`成员变量`service`。

要提供此服务，您必须创建 [``Dictionary.java``](examples/DictionaryServiceDemo/DictionaryServiceProvider/src/dictionary/spi/Dictionary.java)实现。为了简单起见，创建一个只定义几个单词的通用字典。您可以使用数据库，一组属性文件或任何其他技术来实现字典。演示提供者模式的最简单方法是在单个文件中包含所有单词和定义。

以下代码显示了`Dictionary` SPI， [``GeneralDictionary.java``](examples/DictionaryServiceDemo/GeneralDictionary/src/dictionary/GeneralDictionary.java)类的实现。请注意，它提供了无参数构造函数，并实现了 SPI 定义的`getDefinition`方法。

```
package dictionary;

import dictionary.spi.Dictionary;
import java.util.SortedMap;
import java.util.TreeMap;

public class GeneralDictionary implements Dictionary {

    private SortedMap<String, String> map;

    public GeneralDictionary() {
        map = new TreeMap<String, String>();
        map.put(
            "book",
            "a set of written or printed pages, usually bound with " +
                "a protective cover");
        map.put(
            "editor",
            "a person who edits");
    }

    @Override
    public String getDefinition(String word) {
        return map.get(word);
    }

}

```

该示例将已编译的类文件存储在目录`GeneralDictionary/build`中。 **注**：必须在类`GeneralDictionary`之前编译`dictionary.DictionaryService`和`dictionary.spi.Dictionary`类。

此示例的`GeneralDictionary`提供程序仅定义了两个单词： _book_ 和 _editor_ 。显然，更可用的字典将提供更实质的常用词汇表。

为了演示多个提供程序如何实现相同的 SPI，以下代码显示了另一个可能的提供程序。 [``ExtendedDictionary.java`` ](examples/DictionaryServiceDemo/ExtendedDictionary/src/dictionary/ExtendedDictionary.java)服务提供商是一个扩展字典，包含大多数软件开发人员熟悉的技术术语。

```
package dictionary;

import dictionary.spi.Dictionary;
import java.util.SortedMap;
import java.util.TreeMap;

public class ExtendedDictionary implements Dictionary {

        private SortedMap<String, String> map;

    public ExtendedDictionary() {
        map = new TreeMap<String, String>();
        map.put(
            "xml",
            "a document standard often used in web services, among other " +
                "things");
        map.put(
            "REST",
            "an architecture style for creating, reading, updating, " +
                "and deleting data that attempts to use the common " +
                "vocabulary of the HTTP protocol; Representational State " +
                "Transfer");
    }

    @Override
    public String getDefinition(String word) {
        return map.get(word);
    }

}

```

该示例将已编译的类文件存储在目录`ExtendedDictionary/build`中。 **注**：必须在类`ExtendedDictionary`之前编译`dictionary.DictionaryService`和`dictionary.spi.Dictionary`类。

很容易想象客户使用一整套`Dictionary`提供商来满足他们自己的特殊需求。服务加载器 API 使他们能够根据需求或首选项的变化向其应用程序添加新词典。由于底层字处理器应用程序是可扩展的，因此客户无需额外编码即可使用新提供程序。

要注册服务提供者，请创建提供者配置文件，该文件存储在服务提供者的 JAR 文件的`META-INF/services`目录中。配置文件的名称是服务提供者的完全限定类名，其中名称的每个组件用句点（`.`）分隔，嵌套类用美元符号（`$`）分隔。

提供程序配置文件包含服务提供程序的完全限定类名，每行一个名称。该文件必须是 UTF-8 编码的。此外，您可以通过使用数字符号（`#`）开始注释行，在文件中包含注释。

例如，要注册服务提供商`GeneralDictionary`，请创建名为 [``dictionary.spi.Dictionary``](examples/DictionaryServiceDemo/GeneralDictionary/src/META-INF/services/dictionary.spi.Dictionary)的文本文件。该文件包含一行：

```
dictionary.GeneralDictionary

```

同样，要注册服务提供商`ExtendedDictionary`，请创建名为 [``dictionary.spi.Dictionary``](examples/DictionaryServiceDemo/ExtendedDictionary/src/META-INF/services/dictionary.spi.Dictionary)的文本文件。该文件包含一行：

```
dictionary.ExtendedDictionary

```

由于开发完整的字处理器应用程序是一项重要任务，因此本教程提供了一个使用`DictionaryService`和`Dictionary` SPI 的更简单的应用程序。 `DictionaryDemo`样本搜索来自任何`Dictionary`提供者的单词 _book_ ， _editor_ ， _xml_ 和 _REST_ 单词类路径并检索它们的定义。

以下是 [``DictionaryDemo``](examples/DictionaryServiceDemo/DictionaryDemo/src/dictionary/DictionaryDemo.java)样品。它从`DictionaryService`实例请求定义目标字，该实例将请求传递给其已知的`Dictionary`提供者。

```
package dictionary;

import dictionary.DictionaryService;

public class DictionaryDemo {

  public static void main(String[] args) {

    DictionaryService dictionary = DictionaryService.getInstance();
    System.out.println(DictionaryDemo.lookup(dictionary, "book"));
    System.out.println(DictionaryDemo.lookup(dictionary, "editor"));
    System.out.println(DictionaryDemo.lookup(dictionary, "xml"));
    System.out.println(DictionaryDemo.lookup(dictionary, "REST"));
  }

  public static String lookup(DictionaryService dictionary, String word) {
    String outputString = word + ": ";
    String definition = dictionary.getDefinition(word);
    if (definition == null) {
      return outputString + "Cannot find definition for this word.";
    } else {
      return outputString + definition;
    }
  }
}

```

该示例将已编译的类文件存储在目录`DictionaryDemo/build`中。 **注**：必须在类`DictionaryDemo`之前编译`dictionary.DictionaryService`和`dictionary.spi.Dictionary`类。

### 中

有关如何创建 JAR 文件的信息，请参阅 JAR 文件中的[打包程序课程。](../../deployment/jar/index.html)

要打包`GeneralDictionary`服务提供程序，请创建名为`GeneralDictionary/dist/GeneralDictionary.jar`的 JAR 文件，该文件包含此服务提供程序的已编译类文件和以下目录结构中的配置文件：

*   `META-INF`
    *   `services`
        *   `dictionary.spi.Dictionary`
*   `dictionary`
    *   `GeneralDictionary.class`

同样，要打包`ExtendedDictionary`服务提供程序，请创建一个名为`ExtendedDictionary/dist/ExtendedDictionary.jar`的 JAR 文件，该文件包含此服务提供程序的已编译类文件和以下目录结构中的配置文件：

*   `META-INF`
    *   `services`
        *   `dictionary.spi.Dictionary`
*   `dictionary`
    *   `ExtendedDictionary.class`

请注意，提供程序配置文件必须位于 JAR 文件的目录`META-INF/services`中。

创建名为`DictionaryServiceProvider/dist/DictionaryServiceProvider.jar`的 JAR 文件，其中包含以下文件：

*   `dictionary`
    *   `DictionaryService.class`
    *   `spi`
        *   `Dictionary.class`

创建名为`DictionaryDemo/dist/DictionaryDemo.jar`的 JAR 文件，其中包含以下文件：

*   `dictionary`
    *   `DictionaryDemo.class`

以下命令使用`GeneralDictionary`服务提供程序运行`DictionaryDemo`示例：

**Linux 和 Solaris：**

`java -Djava.ext.dirs=../DictionaryServiceProvider/dist:../GeneralDictionary/dist -cp dist/DictionaryDemo.jar dictionary.DictionaryDemo`

**Windows：**

`java -Djava.ext.dirs=..\DictionaryServiceProvider\dist;..\GeneralDictionary\dist -cp dist\DictionaryDemo.jar dictionary.DictionaryDemo`

使用此命令时，假定以下内容：

*   当前目录是`DictionaryDemo`。
*   存在以下 JAR 文件：
    *   `DictionaryDemo/dist/DictionaryDemo.jar`：包含`DictionaryDemo`类
    *   `DictionaryServiceProvider/dist/DictionaryServiceProvider.jar`：包含`Dictionary` SPI 和`DictionaryService`类
    *   `GeneralDictionary/dist/GeneralDictionary.jar`：包含`GeneralDictionary`服务提供商和配置文件

该命令打印以下内容：

```
book: a set of written or printed pages, usually bound with a protective cover
editor: a person who edits
xml: Cannot find definition for this word.
REST: Cannot find definition for this word.

```

假设您运行以下命令并且`ExtendedDictionary/dist/ExtendedDictionary.jar`存在：

**Linux 和 Solaris：**

`java -Djava.ext.dirs=../DictionaryServiceProvider/dist:../ExtendedDictionary/dist -cp dist/DictionaryDemo.jar dictionary.DictionaryDemo`

**Windows：**

`java -Djava.ext.dirs=..\DictionaryServiceProvider\dist;..\ExtendedDictionary\dist -cp dist\DictionaryDemo.jar dictionary.DictionaryDemo`

该命令打印以下内容：

```
book: Cannot find definition for this word.
editor: Cannot find definition for this word.
xml: a document standard often used in web services, among other things
REST: an architecture style for creating, reading, updating, and deleting data that attempts to use the common vocabulary of the HTTP protocol; Representational State Transfer

```

`java.util.ServiceLoader`类可帮助您查找，加载和使用服务提供程序。它在应用程序的类路径或运行时环境的扩展目录中搜索服务提供程序。它加载它们并使您的应用程序能够使用提供程序的 API。如果将新提供程序添加到类路径或运行时扩展目录中，`ServiceLoader`类将查找它们。如果您的应用程序知道提供程序接口，则可以找到并使用该接口的不同实现。您可以使用接口的第一个可加载实例，也可以遍历所有可用接口。

`ServiceLoader`类是 final，这意味着您不能将其作为子类或覆盖其加载算法。例如，您无法更改其算法以从其他位置搜索服务。

从`ServiceLoader`类的角度来看，所有服务都有一个类型，通常是单个接口或抽象类。提供程序本身包含一个或多个具体类，这些类使用特定于其目的的实现来扩展服务类型。 `ServiceLoader`类要求单个公开的提供程序类型具有默认构造函数，该构造函数不需要参数。这使`ServiceLoader`类可以轻松实例化它找到的服务提供者。

提供商按需定位和实例化。服务加载程序维护已加载的提供程序的缓存。每次调用 loader 的`iterator`方法都会返回一个迭代器，它首先以实例化的顺序生成缓存的所有元素。然后，服务加载器定位并实例化任何新提供者，依次将每个提供者添加到缓存中。您可以使用`reload`方法清除提供程序缓存。

要为特定类创建加载器，请将类本身提供给`load`或`loadInstalled`方法。您可以使用默认的类加载器或提供您自己的`ClassLoader`子类。

`loadInstalled`方法搜索运行时环境的已安装运行时提供程序的扩展目录。默认扩展位置是运行时环境的`jre/lib/ext`目录。您应该仅将扩展位置用于众所周知的受信任提供程序，因为此位置将成为所有应用程序的类路径的一部分。在本文中，提供程序不使用扩展目录，而是依赖于特定于应用程序的类路径。

##  的限制

`ServiceLoader` API 很有用，但它有局限性。例如，无法从`ServiceLoader`类派生类，因此您无法修改其行为。您可以使用自定义`ClassLoader`子类来更改类的查找方式，但不能扩展`ServiceLoader`本身。此外，当运行时新的提供程序可用时，当前的`ServiceLoader`类无法告诉您的应用程序。此外，您无法将更改侦听器添加到加载程序以查明是否将新提供程序放入特定于应用程序的扩展目录中。

公共`ServiceLoader` API 在 Java SE 6 中可用。虽然加载器服务早在 JDK 1.3 中就已存在，但 API 是私有的，仅适用于内部 Java 运行时代码。

可扩展应用程序提供可由服务提供商扩展的服务点。创建可扩展应用程序的最简单方法是使用`ServiceLoader`，它可用于 Java SE 6 及更高版本。使用此类，您可以将提供程序实现添加到应用程序类路径以使新功能可用。 `ServiceLoader`类是最终的，因此您无法修改其功能。