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) () -> 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为我们提供了清理缓存的方法。
我们可以定时清理缓存,也可以通过接口调取手动清理缓存,根据自己需求来定。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持IT俱乐部。