ResourceBundle主要是用于和本地语言环境相关的一些资源绑定。特别是String资源。
从国际化的设计角度看,一般在代码里不编写和语言环境相关的东西。比如在代码里编写和语言环境相关的错误提示或信息。
以下面枚举为例:
public enum WeekdayEnum {
Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday;
}
如果期望在不同的weekday的心情,不同语言环境输出不同的信息,如中文环境希望这样输出:
Monday:星期一郁闷
Tuesday:星期二忐忑
Wednesday:星期三难熬
Thursday:星期四期待
Friday星期五激动
Saturday:星期六高兴
Sunday:星期天担忧
这时我们需要定义资源文件,为不同的语言环境制定不同的资源文件,同时支持占位符,可以通过动态传入字符串替换占位符。
JDK自带的ResourceBundle支持properties文件作为资源文件绑定。而我们有的时候希望更习惯于xml的文件定义。这时可以基于JDK ResourceBundl扩展,实现基于XML的ResourceBundl资源绑定。
我们公司里面的二方库有一套xml resourcebundle的实现,但是该实现需要依赖一些外部的jar包,如xml解析等等。
有时候,这种基础功能基于原生实现更方便,不要去依赖其他的二方库,所以实现了一套完全基于JDK的实现。
1.定义资源文件 WeekDayEnum.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<entry key="Monday">星期一郁闷</entry>
<entry key="Tuesday">星期二忐忑</entry>
<entry key="Wednesday">星期三难熬</entry>
<entry key="Thursday">星期四期待</entry>
<entry key="Friday">星期五激动</entry>
<entry key="Saturday">星期六高兴</entry>
<entry key="Sunday">星期天担忧</entry>
</properties>
2.编写代码实现
2.1.XmlResourceFactory
public class XmlResourceBundleFactory {
private static XMLResourceBundleControl DEFAULT_CONTROL = new XMLResourceBundleControl(null,true);
private final static String POINT_REGEX = "\\.";
/**
* 通过默认的XMLResourceBundleControl获取资源Bundle
*
* <pre>
* 默认的XMLResourceBundleControl实现,就在classpath的跟目录下查找资源文件。
* </pre>
*
* @param fileName
* @return
*/
public static ResourceBundle getBundle(String fileName) {
return ResourceBundle.getBundle(fileName, DEFAULT_CONTROL);
}
/**
* 通过指定path的XMLResourceBundleControl获取资源Bundle
*
* <pre>
* 指定path的XMLResourceBundleControl实现,会在指定的classpath目录下查找资源文件。
* </pre>
*
* @param fileName
* @param classpath
* @return
*/
public static ResourceBundle getBundle(String classpath, String fileName) {
XMLResourceBundleControl xmlResourceBundleControl = new XMLResourceBundleControl(classpath,true);
return ResourceBundle.getBundle(fileName, xmlResourceBundleControl);
}
public static class XMLResourceBundleControl extends ResourceBundle.Control {
private String resourcePath; // 資源的classpath路径
private boolean seprateDotPrefix; // 用于标识是否把包含点的baseName前缀过滤掉。
XMLResourceBundleControl(String resourcePath, boolean seprateDotPrefix){
this.resourcePath = resourcePath;
this.seprateDotPrefix = seprateDotPrefix;
}
public List<String> getFormats(String baseName) {
return Collections.singletonList("xml");
}
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader,
boolean reload) throws IllegalAccessException, InstantiationException,
IOException {
if ((baseName == null) || (locale == null) || (format == null) || (loader == null)) {
throw new NullPointerException();
}
ResourceBundle bundle = null;
if (!format.equals("xml")) {
return null;
}
if (seprateDotPrefix) {
if (baseName.indexOf(".") > 0) {
String array[] = baseName.split(POINT_REGEX);
baseName = array[array.length - 1];
}
}
String bundleName = toBundleName(baseName, locale);
String resourceName = toResourceName(bundleName, format);
String resourceFullName = resourceName;
if (resourcePath != null && !"".equals(resourcePath.trim())) {// baseName可能包含包命前綴,把包命前綴去除
resourceFullName = resourcePath + "/" + resourceName;
}
URL url = loader.getResource(resourceFullName);
if (url == null) {
return null;
}
URLConnection connection = url.openConnection();
if (connection == null) {
return null;
}
if (reload) {
connection.setUseCaches(false);
}
InputStream stream = connection.getInputStream();
if (stream == null) {
return null;
}
BufferedInputStream bis = new BufferedInputStream(stream);
bundle = new XMLResourceBundle(bis);
bis.close();
return bundle;
}
}
static class XMLResourceBundle extends ResourceBundle {
private Properties props;
XMLResourceBundle(InputStream stream) throws IOException{
props = new Properties();
props.loadFromXML(stream);
}
protected Object handleGetObject(String key) {
return props.getProperty(key);
}
public Enumeration<String> getKeys() {
Set<String> handleKeys = props.stringPropertyNames();
return Collections.enumeration(handleKeys);
}
}
}
2.2 CodeMessageHolder
public class CodeMessageHolder {
private Class<?> type;
private String resourcePath;
private ResourceBundle resourceBundle;
/**
* 从指定路径加载resource
*
* @param type
* @param classpath
*/
private CodeMessageHolder(Class<?> type, String classpath){
this.type = type;
this.resourcePath = classpath;
loadBundleBySpecialPath();
}
/**
* 从默认路径加载resource
*
* @param type
*/
private CodeMessageHolder(Class<?> type){
this.type = type;
loadBundleByDefault();
}
protected static CodeMessageHolder newDefaultMessageHolder(Class<?> type) {
return new CodeMessageHolder(type);
}
protected static CodeMessageHolder newSpecialPathMessageHolder(Class<?> type,String classpath) {
return new CodeMessageHolder(type,classpath);
}
private void loadBundleByDefault() {
resourceBundle = XmlResourceBundleFactory.getBundle(type.getName());
}
private void loadBundleBySpecialPath() {
resourceBundle = XmlResourceBundleFactory.getBundle(resourcePath, type.getName());
}
public String getMessage(Enum<?> e) {
if (resourceBundle == null) {
return null;
}
return resourceBundle.getString(e.name());
}
public String getMessage(String key) {
if (resourceBundle == null) {
return null;
}
return resourceBundle.getString(key);
}
public String getMessage(Enum<?> e, String... param) {
String s = getMessage(e);
if (s == null) {
return null;
}
return MessageFormat.format(s, (Object[]) param);
}
public String getMessage(String key, String... param) {
String s = getMessage(key);
if (s == null) {
return null;
}
return MessageFormat.format(s, (Object[]) param);
}
}
3.编写枚举测试类,测试绑定
public enum WeekdayEnum {
Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday;
private static CodeMessageHolder messageHolder = CodeMessageHolder.newDefaultMessageHolder(WeekdayEnum.class);
public String getMessage() {
return messageHolder.getMessage(this);
}
public String getMessage(String... params) {
return messageHolder.getMessage(this, params);
}
public static void main(String[] args) {
for (WeekdayEnum w : WeekdayEnum.values()) {
System.out.println(w.getMessage());
}
}
}
关于resourcebundle内部实现,JDK是基于cache来做的,不需要每次都去装载资源文件。内部实现看源码即可明白,这里不过多解释。
附件附上源码实现,刚好看到这个且有兴趣的,欢迎一起交流或提供建议。