# 将自定义资源包安装为扩展 > 原文: [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`: ``` ]> Test data for RBCPTest.java XML ``` 以下是属性文本文件等效: ``` # Test data for RBCPTest.java type = XML ``` 所有 XML 属性文本文件都具有相同的结构: * 指定文档类型定义(DTD)的 DOCTYPE 声明:DTD 定义 XML 文件的结构。 **注意**:您可以在 XML 属性文件中使用以下 DOCTYPE 声明: ``` <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> ``` 导出或导入属性时不访问系统 URI(`http://java.sun.com/dtd/properties.dtd`);它是一个唯一标识 XML 属性文件的 DTD 的字符串。 * 根元素`<properties>`:该元素包含所有其他元素。 * 任意数量的`<comment>`元素:这些元素用于注释。 * 任意数量的`<entry>`元素:使用属性`key`指定密钥;指定`<entry>`标签之间的键值。 有关 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 ```