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 {
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 {
return super.newBundle(baseName, locale, format, loader, reload);
}
}
}
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();
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) {
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.");
}
}
}
return ResourceBundle.getBundle(basename, locale, classLoader);
}
@Scheduled(fixedRate = 180000)
public void clearI18nCache() {
ResourceBundle.clearCache(Objects.requireNonNull(getBundleClassLoader()));
}
}
最后的clearI18nCache方法
因为i18n存在缓存想要外部资源文件修改后生效,清除缓存,我们读取源码不难发现i18n为我们提供了清理缓存的方法。 我们可以定时清理缓存,也可以通过接口调取手动清理缓存,根据自己需求来定。
|