# 将自定义资源包安装为扩展

> 原文： [https://docs.oracle.com/javase/tutorial/i18n/serviceproviders/resourcebundlecontrolprovider.html](https://docs.oracle.com/javase/tutorial/i18n/serviceproviders/resourcebundlecontrolprovider.html)

[Customizing Resource Bundle Loading](../../i18n/resbundle/control.html) 部分向您展示了如何更改资源包的加载方式。这涉及从类 [`ResourceBundle.Control`](https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.Control.html) 派生一个新类，然后通过调用以下方法检索资源包：

```
ResourceBundle getBundle(
  String baseName,
  Locale targetLocale,
  ResourceBundle.Control control)

```

参数`control`是`ResourceBundle.Control`的实现。

[`java.util.spi.ResourceBundleControlProvider`](https://docs.oracle.com/javase/8/docs/api/java/util/spi/ResourceBundleControlProvider.html) 接口使您可以更改以下方法加载资源包的方式：

```
ResourceBundle getBundle(
  String baseName,
  Locale targetLocale)

```

请注意，此版本的 [`ResourceBundle.getBundle`](https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-java.util.Locale-) 方法不需要`ResourceBundle.Control`类的实例。 `ResourceBundleControlProvider`是服务提供者接口（SPI）。 SPI 使您能够创建可扩展的应用程序，这些应用程序可以轻松扩展而无需修改其原始代码库。有关详细信息，请参阅[创建可扩展应用程序](../../ext/basics/spi.html)。

要使用 SPI，首先要实现像`ResourceBundleControlProvider`这样的 SPI 来创建服务提供者。实现 SPI 时，指定它将如何提供服务。 `ResourceBundleControlProvider` SPI 提供的服务是在应用程序调用方法`ResourceBundle.getBundle(String baseName, Locale targetLocale)`时获取适当的`ResourceBundle.Control`实例。使用 [Java 扩展机制](../../ext/index.html)将服务提供程序打包为已安装的扩展。运行应用程序时，不要在类路径中命名扩展名;运行时环境查找并加载这些扩展。

已安装的`ResourceBundleControlProvider` SPI 实现取代了默认的`ResourceBundle.Control`类（定义了默认的 bundle 加载过程）。因此，`ResourceBundleControlProvider`接口使您可以使用任何自定义`ResourceBundle.Control`类，而无需对应用程序的源代码进行任何其他更改。此外，此界面使您无需引用任何自定义`ResourceBundle.Control`类即可编写应用程序。

[``RBCPTest.java`` ](examples/rbcpsample/src/RBCPTest.java)示例显示了如何实现`ResourceBundleControlProvider`接口以及如何将其打包为已安装的扩展。此样本包装在 zip 文件 `[RBCPTest.zip](examples/rbcpsample/RBCPTest.zip)`中，由以下文件组成：

*   `src`
    *   [``java.util.spi.ResourceBundleControlProvider``](examples/rbcpsample/src/java.util.spi.ResourceBundleControlProvider)
    *   [``RBCPTest.java``](examples/rbcpsample/src/RBCPTest.java)
    *   `rbcp`
        *   [``PropertiesResourceBundleControl.java``](examples/rbcpsample/src/rbcp/PropertiesResourceBundleControl.java)
        *   [``PropertiesResourceBundleControlProvider.java``](examples/rbcpsample/src/rbcp/PropertiesResourceBundleControlProvider.java)
        *   [``XMLResourceBundleControl.java``](examples/rbcpsample/src/rbcp/XMLResourceBundleControl.java)
        *   [``XMLResourceBundleControlProvider.java``](examples/rbcpsample/src/rbcp/XMLResourceBundleControlProvider.java)
    *   `resources`
        *   [``RBControl.properties``](examples/rbcpsample/src/resources/RBControl.properties)
        *   [``RBControl_zh.properties``](examples/rbcpsample/src/resources/RBControl_zh.properties)
        *   [``RBControl_zh_CN.properties``](examples/rbcpsample/src/resources/RBControl_zh_CN.properties)
        *   [``RBControl_zh_HK.properties``](examples/rbcpsample/src/resources/RBControl_zh_HK.properties)
        *   [``RBControl_zh_TW.properties``](examples/rbcpsample/src/resources/RBControl_zh_TW.properties)
        *   [``XmlRB.xml``](examples/rbcpsample/src/resources/XmlRB.xml)
        *   [``XmlRB_ja.xml``](examples/rbcpsample/src/resources/XmlRB_ja.xml)
*   `lib`
    *   [`rbcontrolprovider.jar`](#package-provider)
*   `build`：包含`rbcontrolprovider.jar`中打包的所有文件以及类文件`RBCPTest.class`
*   [``build.xml``](examples/rbcpsample/build.xml)

以下步骤说明如何重新创建文件`RBCPTest.zip`的内容，`RBCPTest`示例的工作方式以及如何运行它：

1.  [创建 ResourceBundle.Control 类的实现。](#create-implementations-of-resourcebundle.control)
2.  [实现 ResourceBundleControlProvider 接口。](#implement-resourcebundlecontrolprovider)
3.  [在您的应用程序中，调用 ResourceBundle.getBundle 方法。](#invoke-resourcebundle.getbundle)
4.  [通过创建配置文件注册服务提供商。](#register-service-provider)
5.  [将提供程序，其必需的类和配置文件打包在 JAR 文件中。](#package-provider)
6.  [运行 RBCPTest 程序。](#run-rbcptest)

[``RBCPTest.java`` ](examples/rbcpsample/src/RBCPTest.java)样本使用`ResourseBundle.Control`的两个实现：

*   [``PropertiesResourceBundleControlProvider.java`` ](examples/rbcpsample/src/rbcp/PropertiesResourceBundleControlProvider.java)：这与[自定义资源包加载](../resbundle/control.html)中定义的`ResourceBundle.Control`实现相同。

*   [``XMLResourceBundleControl.java`` ](examples/rbcpsample/src/rbcp/XMLResourceBundleControl.java)：此`ResourceBundle.Control`实现使用方法 [`Properties.loadFromXML`](https://docs.oracle.com/javase/8/docs/api/java/util/Properties.html#loadFromXML-java.io.InputStream-) 加载基于 XML 的包。

### XML 属性文件

如[使用属性文件](../resbundle/propfile.html)备份 ResourceBundle 一节中所述，属性文件是简单的文本文件。它们在每一行包含一个键值对。 XML 属性文件就像属性文件一样：它们包含键值对，除了它们具有 XML 结构。以下是 XML 属性文件`XmlRB.xml`：

```
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE properties [
<!ELEMENT properties ( comment?, entry* ) >
<!ATTLIST properties version CDATA #FIXED "1.0">
<!ELEMENT comment (#PCDATA) >
<!ELEMENT entry (#PCDATA) >
<!ATTLIST entry key CDATA #REQUIRED>
]>

<properties>
    <comment>Test data for RBCPTest.java</comment>
    <entry key="type">XML</entry>
</properties>

```

以下是属性文本文件等效：

```
# Test data for RBCPTest.java
type = XML

```

所有 XML 属性文本文件都具有相同的结构：

*   指定文档类型定义（DTD）的 DOCTYPE 声明：DTD 定义 XML 文件的结构。 **注意**：您可以在 XML 属性文件中使用以下 DOCTYPE 声明：

    ```
    &lt;!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"&gt;  

    ```

    导出或导入属性时不访问系统 URI（`http://java.sun.com/dtd/properties.dtd`）;它是一个唯一标识 XML 属性文件的 DTD 的字符串。

*   根元素`&lt;properties&gt;`：该元素包含所有其他元素。

*   任意数量的`&lt;comment&gt;`元素：这些元素用于注释。

*   任意数量的`&lt;entry&gt;`元素：使用属性`key`指定密钥;指定`&lt;entry&gt;`标签之间的键值。

有关 XML 属性文件的更多信息，请参见 [`Properties`](https://docs.oracle.com/javase/8/docs/api/java/util/Properties.html) 类。

该接口包含一种方法， [`ResourceBundle.Control getControl(String baseName)`](https://docs.oracle.com/javase/8/docs/api/java/util/spi/ResourceBundleControlProvider.html#getControl-java.lang.String-) 方法。参数`baseName`是资源包的名称。在`getBundle`的方法定义中，指定在给定资源包名称的情况下应返回的`ResourceBundle.Control`实例。

`RBCPTest`样品包含`ResourceBundleControlProvider`界面的两种实现方式， [``PropertiesResourceBundleControlProvider.java``](examples/rbcpsample/src/rbcp/PropertiesResourceBundleControlProvider.java)和 [``XMLResourceBundleControlProvider.java``](examples/rbcpsample/src/rbcp/XMLResourceBundleControlProvider.java)。如果资源包的基本名称以`resources.RBControl`开头，则`PropertiesResourceBundleControlProvider.getBundle`方法返回`PropertiesResourceBundleControl`的实例（在此示例中，所有资源文件都包含在包`resources`中）：

```
package rbcp;

import java.util.ResourceBundle;
import java.util.spi.ResourceBundleControlProvider;

public class PropertiesResourceBundleControlProvider
    implements ResourceBundleControlProvider {
    static final ResourceBundle.Control PROPERTIESCONTROL =
        new PropertiesResourceBundleControl();

    public ResourceBundle.Control getControl(String baseName) {
        System.out.println("Class: " + getClass().getName() + ".getControl");
        System.out.println("    called for " + baseName);

        // Throws a NPE if baseName is null.
        if (baseName.startsWith("resources.RBControl")) {
            System.out.println("    returns " + PROPERTIESCONTROL);
            return PROPERTIESCONTROL;
        }
        System.out.println("    returns null");
        System.out.println();
        return null;
    }
}

```

类似地，如果资源包的基本名称以`resources.Xml`开头，则方法`XMLResourceBundleControlProvider.getControl`返回`XMLResourceBundleControl`的实例。

**注**：您可以创建`ResourceBundleControlProvider`接口的一个实现，它根据基本名称返回`PropertiesResourceBundleControl`或`XMLResourceBundleControl`的实例。

类`RBCPTest`使用方法 [`ResourceBundle.getBundle`](https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-java.util.Locale-) 检索资源包：

```
import java.io.*;
import java.net.*;
import java.util.*;

public class RBCPTest {
    public static void main(String[] args) {
        ResourceBundle rb = ResourceBundle.getBundle(
            "resources.XmlRB", Locale.ROOT);
        String type = rb.getString("type");
        System.out.println("Root locale. Key, type: " + type);
        System.out.println();

        rb = ResourceBundle.getBundle("resources.XmlRB", Locale.JAPAN);
        type = rb.getString("type");
        System.out.println("Japan locale. Key, type: " + type);
        System.out.println();

        test(Locale.CHINA);
        test(new Locale("zh", "HK"));
        test(Locale.TAIWAN);
        test(Locale.CANADA);
    }

    private static void test(Locale locale) {
        ResourceBundle rb = ResourceBundle.getBundle(
            "resources.RBControl", locale);
        System.out.println("locale: " + locale);
        System.out.println("    region: " + rb.getString("region"));
        System.out.println("    language: " + rb.getString("language"));
        System.out.println();
    }
}

```

请注意，此类中不会出现`ResourceBundle.Control`或`ResourceBundleControlProvider`的实现。由于`ResourceBundleControlProvider`接口使用 Java 扩展机制，因此运行时环境会查找并加载这些实现。但是，`ResourceBundleControlProvider`实现和使用 Java 扩展机制安装的其他服务提供程序使用 [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) 类加载。使用此类意味着您必须使用配置文件注册服务提供程序，这将在下一步中介绍。

配置文件的名称是提供程序实现的接口或类的完全限定名称。配置文件包含提供程序的完全限定类名。文件 [``java.util.spi.ResourceBundleControlProvider``](examples/rbcpsample/src/java.util.spi.ResourceBundleControlProvider)包含`PropertiesResourceBundleControlProvider`和`XMLResourceBundleControlProvider`的完全限定名称，每行一个名称：

```
rbcp.XMLResourceBundleControlProvider
rbcp.PropertiesResourceBundleControlProvider

```

编译源文件。从包含文件`build.xml`的目录中，运行以下命令：

```
javac -d build src/java.* src/rbcp/*.java
```

此命令将编译`src`目录中包含的源文件，并将类文件放在`build`目录中。在 Windows 上，请确保使用反斜杠（`\`）分隔目录和文件名。

创建一个 JAR 文件，其中包含以下目录结构中的已编译类文件，资源文件和配置文件：

*   `META-INF`
    *   `services`
        *   `java.util.spi.ResourceBundleControlProvider`
*   `rbcp`
    *   `PropertiesResourceBundleControl.class`
    *   `PropertiesResourceBundleControlProvider.class`
    *   `XMLResourceBundleControl.class`
    *   `XMLResourceBundleControlProvider.class`
*   `resources`
    *   `RBControl.properties`
    *   `RBControl_zh.properties`
    *   `RBControl_zh_CN.properties`
    *   `RBControl_zh_HK.properties`
    *   `RBControl_zh_TW.properties`
    *   `XmlRB.xml`
    *   `XmlRB_ja.xml`

请注意，配置文件`java.util.spi.ResourceBundleControlProvider`必须打包在目录`/META-INF/services`中。此示例将这些文件打包在`lib`目录中的 JAR 文件`rbcontrolprovider.jar`中。

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

或者，下载并安装 [Apache Ant](http://ant.apache.org/) ，这是一个使您能够自动化构建过程的工具，例如编译 Java 文件和创建 JAR 文件。确保 Apache Ant 可执行文件位于`PATH`环境变量中，以便您可以从任何目录运行它。安装 Apache Ant 后，请按照下列步骤操作：

1.  编辑文件 [``build.xml``](examples/rbcpsample/build.xml)并将`${JAVAC}`更改为 Java 编译器的完整路径名，`javac`和`${JAVA}`为 Java 运行时的完整路径名可执行文件，`java`。

2.  从包含文件`build.xml`的同一目录运行以下命令：

    ```
    ant jar
    ```

    此命令编译 Java 源文件，并将它们以及所需的资源和配置文件打包到`lib`目录中的 JAR 文件`rbcontrolprovider.jar`中。

在命令提示符处，从包含`build.xml`文件的目录运行以下命令：

```
java -Djava.ext.dirs=lib -cp build RBCPTest
```

此命令假定以下内容：

*   包含 RBCPTest 示例的已编译代码的 JAR 文件位于`lib`目录中。
*   已编译的类`RBCPTest.class`位于`build`目录中。

或者，使用 Apache Ant 并从包含`build.xml`文件的目录运行以下命令：

```
ant run
```

安装 Java 扩展时，通常将扩展的 JAR 文件放在 JRE 的`lib/ext`目录中。但是，此命令指定包含具有系统属性`java.ext.dirs`的 Java 扩展的目录。

`RBCPTest`程序首先尝试检索基本名称为`resources.XmlRB`和语言环境`Locale.ROOT`和`Local.JAPAN`的资源包。检索这些资源包的程序输出类似于以下内容：

```
Class: rbcp.XMLResourceBundleControlProvider.getControl
    called for resources.XmlRB
    returns rbcp.XMLResourceBundleControl@16c1857
Root locale. Key, type: XML

Class: rbcp.XMLResourceBundleControlProvider.getControl
    called for resources.XmlRB
    returns rbcp.XMLResourceBundleControl@16c1857
Japan locale. Key, type: Value from Japan locale

```

程序成功获取`XMLResourceBundleControl`的实例并访问属性文件`XmlRB.xml`和`XmlRB_ja.xml`。

当`RBCPTest`程序尝试检索资源包时，它会调用配置文件`java.util.spi.ResourceBundleControlProvider`中定义的所有类。例如，当程序检索具有基本名称`resources.RBControl`和区域设置`Locale.CHINA`的资源包时，它将打印以下输出：

```
Class: rbcp.XMLResourceBundleControlProvider.getControl
    called for resources.RBControl
    returns null

Class: rbcp.PropertiesResourceBundleControlProvider.getControl
    called for resources.RBControl
    returns rbcp.PropertiesResourceBundleControl@1ad2911
locale: zh_CN
    region: China
    language: Simplified Chinese

```