2. ResourceBundle 基础

ResourceBundle 是 Java 中管理国际化(i18n)资源的核心工具。它能让我们轻松实现多语言应用,让不同地区用户都能用母语操作程序。

核心要点:

  • ✅ 所有资源文件必须放在同一目录下,且共享基础名称
  • ✅ 文件命名规则:基础名[_语言[_国家[_平台]]].扩展名
  • ❌ 避免硬编码界面文本(如按钮、标签)

典型文件命名示例:

ExampleResource          // 默认文件
ExampleResource_en       // 英语
ExampleResource_en_US    // 美国英语
ExampleResource_en_US_UNIX // 美国英语+UNIX平台

2.1 属性文件方式 (PropertyResourceBundle)

属性文件以 .properties 结尾,采用键值对存储数据。支持三种格式:

# 注释方式1:井号开头
continueButton continue

# 注释方式2:感叹号开头
! 标签区域
helloLabel:hello

# 等号方式(最常用)
cancelButton=cancel

⚠️ 注意:键值对区分大小写,注释以 #! 开头

2.2 Java 文件方式 (ListResourceBundle)

需要继承 ListResourceBundle 并重写 getContents() 方法:

public class ExampleResource_pl_PL extends ListResourceBundle {

    @Override
    protected Object[][] getContents() {
        return new Object[][] {
          {"currency", "polish zloty"},
          {"toUsdRate", new BigDecimal("3.401")},
          {"cities", new String[] { "Warsaw", "Cracow" }} 
        };
    }
}

两种方式对比

  • 🏆 Java 文件优势:可存储任意对象类型(非仅字符串)
  • 🏆 属性文件优势:修改后无需重新编译

3. 资源包使用实战

使用资源包只需三步:

// 1. 定义目标语言环境
Locale locale = new Locale("pl", "PL");

// 2. 加载资源包
ResourceBundle exampleBundle = ResourceBundle.getBundle(
    "com.example.ExampleResource", 
    locale
);

// 3. 获取资源
assertEquals(exampleBundle.getString("currency"), "polish zloty");
assertEquals(exampleBundle.getObject("toUsdRate"), new BigDecimal("3.401")); 
assertArrayEquals(exampleBundle.getStringArray("cities"), 
    new String[]{"Warsaw", "Cracow"});

常用方法:

  • getString(key):获取字符串
  • getObject(key):获取任意对象
  • getStringArray(key):获取字符串数组

4. 资源文件匹配规则

当请求特定语言环境时,JVM 按以下顺序查找文件:

Label_pl_PL_UNIX  // 最精确匹配
Label_pl_PL       // 国家匹配
Label_pl          // 语言匹配
Label_en_US       // 回退到默认环境(美国英语)
Label_en          // 默认语言
Label             // 终极回退(无语言标识)

⚠️ 重要原则:

  1. .java 文件优先于 .properties 文件
  2. 找不到匹配文件时抛出 MissingResourceException
  3. 默认语言环境由 JVM 设置决定

5. 资源继承机制

资源包支持属性继承:子文件会自动继承父文件的键值对。

示例配置:

# resource.properties(父文件)
cancelButton = cancel

# resource_pl.properties(继承父文件)
continueButton = dalej

# resource_pl_PL.properties(继承前两者)
backButton = cofnij

当加载 Locale("pl", "PL") 时:

  • ✅ 同时获取三个文件的所有键值对
  • ❌ 不会回退到默认语言环境

5.1 跨类型继承限制

重要踩坑:属性文件和 Java 文件之间不支持继承

// CustomListResourceBundle.java
public class CustomListResourceBundle extends ListResourceBundle {
    @Override
    protected Object[][] getContents() {
        return new Object[][] {
            { "customMessage", "This is a custom message." }
        };
    }
}

测试验证:

@Test
public void givenListResourceBundle_whenUsingInheritance_thenItShouldNotInherit() {
    ResourceBundle listBundle = ResourceBundle.getBundle(
        "com.example.CustomListResourceBundle", 
        Locale.ENGLISH
    );

    // 只能获取自身定义的键
    assertEquals("This is a custom message.", 
        listBundle.getString("customMessage"));

    // 尝试获取属性文件中的键会抛异常
    assertThrows(MissingResourceException.class, 
        () -> listBundle.getString("greeting"));
}

6. 自定义加载控制

通过继承 ResourceBundle.Control 可自定义加载行为:

public class ExampleControl extends ResourceBundle.Control {

    @Override
    public List<Locale> getCandidateLocales(String s, Locale locale) {
        // 强制只返回波兰语环境
        return Arrays.asList(new Locale("pl", "PL"));
    }
}

应用自定义控制:

ResourceBundle bundle = ResourceBundle.getBundle(
    "com.example.ExampleResource", 
    locale, 
    new ExampleControl()
);

7. UTF-8 编码处理

JDK 8 及更早版本的坑

  • PropertyResourceBundle 默认使用 ISO-8859-1 编码
  • 非拉丁字符(如中文、波兰语)需转义为 \uxxxx

处理方案:

# 使用 native2ascii 工具转换
native2ascii -encoding UTF-8 utf8.properties nonUtf8.properties

转换示例:

# 转换前
polishHello=cześć

# 转换后
polishHello=cze\u015b\u0107

JDK 9+ 的福音:默认支持 UTF-8 编码,无需转换!

8. 总结

ResourceBundle 是 Java 国际化的核心解决方案,关键优势:

  1. 灵活存储:支持属性文件和 Java 类两种方式
  2. 自动继承:子文件自动继承父文件资源
  3. 动态加载:修改属性文件无需重新编译
  4. 精确匹配:智能的语言环境查找机制

使用建议:

  • 优先使用 .properties 文件(修改无需编译)
  • 复杂对象或 JDK 8 环境考虑 Java 类方式
  • JDK 9+ 直接使用 UTF-8 编码
  • 通过自定义 Control 实现特殊加载逻辑

通过合理使用 ResourceBundle,可以轻松构建支持多语言的健壮应用,同时保持代码的可维护性。


原始标题:A Guide to the ResourceBundle | Baeldung

» 下一篇: JPA 高级标签实现