i18n无法读取jar包外国际化文件的根本原因
首先我们看一下i18n是如何绑定资源文件路径的.
绑定资源文件路径的方法是通过下面方法绑定的。
resourcebundle.getbundle()
我们查看源码:
最终发现i18n是通过类加载器加载国际化文件的。

然而类加载器是不能加载jar包外的资源文件的,所以我们要改变加载资源文件的方式,我们可以通过file加载jar包外的资源文件。
改变文件读取方式
我们读取源码发现,i18n通过将资源文件读取为stream流存储在resourcebundle对象中,同时i18n存在缓存,将产生的stream对象存储在缓存中。

首先重写下面方法,修改i18n的读取方式。
这样我们就可以读取到jar包外面的资源文件了。
private class i18nmessagesourcecontrol extends resourcebundle.control {
@override
@nullable
public resourcebundle newbundle(string basename, locale locale, string format, classloader loader, boolean reload)
throws illegalaccessexception, instantiationexception, ioexception {
// special handling of default encoding
if (format.equals("java.properties")) {
string bundlename = tobundlename(basename, locale);
final string resourcename = toresourcename(bundlename, "properties");
inputstream inputstream;
try {
inputstream = accesscontroller.doprivileged((privilegedexceptionaction<inputstream>) () -> getbufferedinputstream(resourcename));
} catch (privilegedactionexception ex) {
throw (ioexception) ex.getexception();
}
if (inputstream != null) {
string encoding = getdefaultencoding();
if (encoding != null) {
try (inputstreamreader bundlereader = new inputstreamreader(inputstream, encoding)) {
return loadbundle(bundlereader);
}
} else {
try (inputstream bundlestream = inputstream) {
return loadbundle(bundlestream);
}
}
} else {
return null;
}
} else {
// delegate handling of "java.class" format to standard control
return super.newbundle(basename, locale, format, loader, reload);
}
}
}
/**
* 拼接url 并返回输入流
*/
public inputstream getbufferedinputstream(string resourcename) throws filenotfoundexception {
string fileurl = system.getproperty("user.dir")+ "/"+ resourcename;
system.out.println(fileurl+"..*");
file file = new file(fileurl);
if (file.exists()) {
return new fileinputstream(file);
}
return null;
}
寻找切入点
我们不难发现,resourcebundle.getbundle()这个方法就是为了获取一个resourcebundle对象,所以我们可以重写dogetbundle方法从而获取resourcebundle对象。
public class i18nconfig extends resourcebundlemessagesource {
private final static logger logger = loggerfactory.getlogger(i18nconfig.class);
@nullable
private volatile i18nmessagesourcecontrol control = new i18nmessagesourcecontrol();
/**
* obtain the resource bundle for the given basename and locale.
*
* @param basename the basename to look for
* @param locale the locale to look for
* @return the corresponding resourcebundle
* @throws missingresourceexception if no matching bundle could be found
* @see java.util.resourcebundle#getbundle(string, locale, classloader)
* @see #getbundleclassloader()
*/
public resourcebundle dogetbundle(string basename, locale locale) throws missingresourceexception {
classloader classloader = getbundleclassloader();
assert.state(classloader != null, "no bundle classloader set");
i18nmessagesourcecontrol control = this.control;
if (control != null) {
try {
return resourcebundle.getbundle(basename, locale, classloader, control);
} catch (unsupportedoperationexception ex) {
// probably in a jigsaw environment on jdk 9+
this.control = null;
string encoding = getdefaultencoding();
if (encoding != null && logger.isinfoenabled()) {
logger.info("resourcebundlemessagesource is configured to read resources with encoding '" +
encoding + "' but resourcebundle.control not supported in current system environment: " +
ex.getmessage() + " - falling back to plain resourcebundle.getbundle retrieval with the " +
"platform default encoding. consider setting the 'defaultencoding' property to 'null' " +
"for participating in the platform default and therefore avoiding this log message.");
}
}
}
// fallback: plain getbundle lookup without control handle
return resourcebundle.getbundle(basename, locale, classloader);
}
@scheduled(fixedrate = 180000)
public void cleari18ncache() {
resourcebundle.clearcache(objects.requirenonnull(getbundleclassloader()));
}
}
最后的cleari18ncache方法
因为i18n存在缓存想要外部资源文件修改后生效,清除缓存,我们读取源码不难发现i18n为我们提供了清理缓存的方法。
我们可以定时清理缓存,也可以通过接口调取手动清理缓存,根据自己需求来定。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论