【SpringBoot集成I18n国际化文件在jar包外生效】

本文介绍了如何使SpringBoot的I18n国际化文件在jar包外部生效,解决了替换jar包内文件的不便。问题在于`spring.messages.basename`配置的classpath限制。解决方案是创建自定义的MessageSourceControl类,修改类加载器读取输入流的方式,使应用能够读取jar包外的配置文件。完整代码和详细步骤文中均有提供。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

问题描述

公司最近出了个需求,是i18n国际化文件需要在springboot生成的jar包外生效.集成i18n国际化网上的文章很多就不在赘述了.但是一直无法在jar包外生效.因为每次都要替换jar包里面的文件比较麻烦.从部署程序的需求上来说,倒是比较合理.所以记录一下解决过程.

不生效的原因

我们通过配置文件中的key"spring.messages.basename",找到对应的使用类ResourceBundleCondition在这里插入图片描述
我们看到了,问题就出现在最下面的红框classpath*了,因为用的是classpath,而不是filepath,所以限制了只能在jar包里面被找到.

解决办法

既然原因找到了,那就要考虑如何解决了.
我开发的时候先是使用i18n加强功能基础上开发的,之前的一些i18n加强功能已经有其他文章介绍了,就不摘抄了(文章后面会有完成的类源码,包含了此部分)
加强i18n功能.文章链接: https://segmentfault.com/a/1190000014538512
上面链接里面写了一个MessageResourceExtension类(下面会用到),继承了ResourceBundleMessageSource.
跟着代码断点查找发现用了org.springframework.context.MessageSource.getMessage(String code, @Nullable Object[] args, Locale locale)
中间的断点不进行一步一步追踪了.
最后发现是在java.util.ResourceBundle#loadBundle中加载的i18n文件
代码如下:

private static ResourceBundle loadBundle(CacheKey cacheKey,
                                             List<String> formats,
                                             Control control,
                                             boolean reload) {
   

        // Here we actually load the bundle in the order of formats
        // specified by the getFormats() value.
        Locale targetLocale = cacheKey.getLocale();

        ResourceBundle bundle = null;
        int size = formats.size();
        for (int i = 0; i < size; i++) {
   
            String format = formats.get(i);
            try {
   
                bundle = control.newBundle(cacheKey.getName(), targetLocale, format,
                                           cacheKey.getLoader(), reload);
            } catch (LinkageError error) {
   
                // We need to handle the LinkageError case due to
                // inconsistent case-sensitivity in ClassLoader.
                // See 6572242 for details.
                cacheKey.setCause(error);
            } catch (Exception cause) {
   
                cacheKey.setCause(cause);
            }
            if (bundle != null) {
   
                // Set the format in the cache key so that it can be
                // used when calling needsReload later.
                cacheKey.setFormat(format);
                bundle.name = cacheKey.getName();
                bundle.locale = targetLocale;
                // Bundle provider might reuse instances. So we should make
                // sure to clear the expired flag here.
                bundle.expired = false;
                break;
            }
        }

        return bundle;
    }

不难发现control.newBundle方法才是关键,spring的org.springframework.context.support.ResourceBundleMessageSource.MessageSourceControl就是继承了此类,但是,我们需要自己做国际化路径文件的配置,怎么才能把自己的类注入呢?
向上翻找发现org.springframework.context.support.ResourceBundleMessageSource#getResourceBundle(String basename, Locale locale)如图:
在这里插入图片描述
所以我们首先要写一个容纳自定义的国际化文件路径的类,参照spring的org.springframework.context.support.ResourceBundleMessageSource.MessageSourceControl就好,之后自己的改造一下,以下为我自己的代码:

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");
                final ClassLoader classLoader = loader;
                final boolean reloadFlag = reload;
                InputStream inputStream;
                try {
   
                    inputStream = AccessController.doPrivileged((PrivilegedExceptionAction<InputStream>) () -> {
   
                        InputStream is = null;
                        if (reloadFlag) {
   
                            URL url = classLoader.getResource(resourceName);
                            if (url != null) {
   
                                URLConnection connection = url.openConnection();
                                if (connection != null) {
   
                                    connection.setUseCaches(false);
                                    is = connection.getInputStream();
                                    // 如果jar包部署存在同级目录下国际化文件,优先读取同级目录文件
                                    if (url.getProtocol().equalsIgnoreCase("jar")) {
   
                                        is = getBufferedInputStream(resourceName, is);
                                    }
                                }
                            }
                        }
                        else {
   
                            is = classLoader.getResourceAsStream(resourceName);
                            // 如果jar包部署存在同级目录下国际化文件,优先读取同级目录文件
                            URL url = classLoader.getResource(resourceName);
                            if (url.getProtocol().equalsIgnoreCase("jar")) {
   
                                is = getBufferedInputStream(resourceName, is);
                            }
                        }
                        return is;
                    });
                }
                catch (PrivilegedActionException ex) {
   
                    throw (IOException) ex.getException();
                
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值