638.md 15.5 KB
Newer Older
W
init  
wizardforcel 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
# 将自定义资源包安装为扩展

> 原文: [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)

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

W
wizardforcel 已提交
30
已安装的`ResourceBundleControlProvider` SPI 实现取代了默认的`ResourceBundle.Control`类(定义了默认的 bundle 加载过程)。因此,`ResourceBundleControlProvider`接口使您可以使用任何自定义`ResourceBundle.Control`类,而无需对应用程序的源代码进行任何其他更改。此外,此接口使您无需引用任何自定义`ResourceBundle.Control`类即可编写应用程序。
W
init  
wizardforcel 已提交
31

W
wizardforcel 已提交
32
[``RBCPTest.java`` ](examples/rbcpsample/src/RBCPTest.java)示例显示了如何实现`ResourceBundleControlProvider`接口以及如何将其打包为已安装的扩展。此样例包装在 zip 文件 `[RBCPTest.zip](examples/rbcpsample/RBCPTest.zip)`中,由以下文件组成:
W
init  
wizardforcel 已提交
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

*   `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)
W
wizardforcel 已提交
61
5.  [将供应器,其必需的类和配置文件打包在 JAR 文件中。](#package-provider)
W
init  
wizardforcel 已提交
62 63
6.  [运行 RBCPTest 程序。](#run-rbcptest)

W
wizardforcel 已提交
64
[``RBCPTest.java`` ](examples/rbcpsample/src/RBCPTest.java)样例使用`ResourseBundle.Control`的两个实现:
W
init  
wizardforcel 已提交
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120

*   [``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`实例。

W
wizardforcel 已提交
121
`RBCPTest`样例包含`ResourceBundleControlProvider`接口的两种实现方式, [``PropertiesResourceBundleControlProvider.java``](examples/rbcpsample/src/rbcp/PropertiesResourceBundleControlProvider.java)[``XMLResourceBundleControlProvider.java``](examples/rbcpsample/src/rbcp/XMLResourceBundleControlProvider.java)。如果资源包的基本名称以`resources.RBControl`开头,则`PropertiesResourceBundleControlProvider.getBundle`方法返回`PropertiesResourceBundleControl`的实例(在此示例中,所有资源文件都包含在包`resources`中):
W
init  
wizardforcel 已提交
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192

```
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();
    }
}

```

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

W
wizardforcel 已提交
195
配置文件的名称是供应器实现的接口或类的完全限定名称。配置文件包含供应器的完全限定类名。文件 [``java.util.spi.ResourceBundleControlProvider``](examples/rbcpsample/src/java.util.spi.ResourceBundleControlProvider)包含`PropertiesResourceBundleControlProvider``XMLResourceBundleControlProvider`的完全限定名称,每行一个名称:
W
init  
wizardforcel 已提交
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296

```
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

```