SpringBoot集成I18n国际化文件在jar包外生效问题

 更新时间:2024年04月03日 10:58:20   作者:锋璠  
这篇文章主要介绍了SpringBoot集成I18n国际化文件在jar包外生效问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
(福利推荐:【腾讯云】服务器最新限时优惠活动,云服务器1核2G仅99元/年、2核4G仅768元/3年,立即抢购>>>:9i0i.cn/qcloud

(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅2998元/3年,立即抢购>>>:9i0i.cn/aliyun

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为我们提供了清理缓存的方法。

我们可以定时清理缓存,也可以通过接口调取手动清理缓存,根据自己需求来定。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持程序员之家。

相关文章

最新评论

?


http://www.vxiaotou.com