• [spring源码学习]八、IOC源码-messageSource


    一、代码实例

      我们在第八章可以看到,spring的context在初始化的时候,会默认调用系统中的各种约定好的bean,其中第一个bean就是id为messageSource的bean,我们了解这应该是一个读取properties的,并支持国际化的bean

    1、首先我们定义这个bean,spring中默认提供了一些类,查了下主要是ResourceBundleMessageSource和ReloadableResourceBundleMessageSource,我们这里采用ResourceBundleMessageSource

            <bean id="messageSource"
                class="org.springframework.context.support.ResourceBundleMessageSource">
                <property name="basenames">
                    <list>
                        <value>messages</value>
                    </list>
                </property>
            </bean>

    2、定义两个messages文件,分别是messages_zh_CN.properties、messages_en.properties和messages.properties,中文环境的配置文件,英文环境和全局配置,内容分别为

    info={0}去上学    //messages_zh_CN.properties
    info={0} go to school //messages_en.properties
    info={0} hehe //messages.properties

    3、测试

    public class Test {
        public static void main(String[] args) {
            ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
            String info1=context.getMessage("info", new String[]{"张三"}, Locale.getDefault());
            String info2=context.getMessage("info", new String[]{"张三"}, Locale.ENGLISH);
            String info3=context.getMessage("info", new String[]{"张三"}, Locale.JAPAN);
            
            System.out.println("info1="+info1);
            System.out.println("info2="+info2);
            System.out.println("info3="+info3);
        }
    }

    4、测试结果

    info1=张三去上学
    info2=张三 go to school
    info3=张三去上学

    可以看到,我们定义了中文和英文环境顺利的找到了对应的文件,可是日文环境没有定义,也没有找到默认的环境,而是使用了系统环境,也就是中文环境

    二、代码分析

    1、先回到上一章的第5部分,context的解析中,将messageSource放到context中

    protected void initMessageSource() {
            ConfigurableListableBeanFactory beanFactory = getBeanFactory();
            //查找是否包含了名为messageSource的bean,如果没有,创建一个默认的
            if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
                this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
                // Make MessageSource aware of parent MessageSource.
                //判断是否有父类且是一个分层级的messageSource,如果是将父容器的的messageSource设置到里边
                if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
                    HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
                    if (hms.getParentMessageSource() == null) {
                        // Only set parent context as parent MessageSource if no parent MessageSource
                        // registered already.
                        hms.setParentMessageSource(getInternalParentMessageSource());
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Using MessageSource [" + this.messageSource + "]");
                }
            }
            else {
                // Use empty MessageSource to be able to accept getMessage calls.
                //初始化一个空的messagesource到context中
                DelegatingMessageSource dms = new DelegatingMessageSource();
                dms.setParentMessageSource(getInternalParentMessageSource());
                this.messageSource = dms;
                beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
                if (logger.isDebugEnabled()) {
                    logger.debug("Unable to locate MessageSource with name '" + MESSAGE_SOURCE_BEAN_NAME +
                            "': using default [" + this.messageSource + "]");
                }
            }
        }

    2、开始通过context.getMessage("info", new String[]{"张三"}, Locale.getDefault());获得信息,其实就是调用了bean-messageSource的getMessage方法

        public String getMessage(String code, Object args[], Locale locale) throws NoSuchMessageException {
            return getMessageSource().getMessage(code, args, locale);
        }

    3、分为两步:在messageSource中查找和查找系统默认的

        public final String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException {
            //到messageSource内查找
            String msg = getMessageInternal(code, args, locale);
            if (msg != null) {
                return msg;
            }
            //查找默认的Message
            String fallback = getDefaultMessage(code);
            if (fallback != null) {
                return fallback;
            }
            throw new NoSuchMessageException(code, locale);
        }

    4、对整个文字的处理,我们可以看到分为有参数和无参数两种,如果无参数,直接解析为最终结果,如果有参数首先解析配置文件,得到messageFormat,然后替换参数

        protected String getMessageInternal(String code, Object[] args, Locale locale) {
            if (code == null) {
                return null;
            }
            if (locale == null) {
                locale = Locale.getDefault();
            }
            Object[] argsToUse = args;
            //是否每次都要重新创建message args是否为空
            //此参数可以配置,可见如果是false且参数为空的话,可以使用缓存
            if (!isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args)) {
                // Optimized resolution: no arguments to apply,
                // therefore no MessageFormat needs to be involved.
                // Note that the default implementation still uses MessageFormat;
                // this can be overridden in specific subclasses.
                String message = resolveCodeWithoutArguments(code, locale);
                if (message != null) {
                    return message;
                }
            }
    
            else {
                // Resolve arguments eagerly, for the case where the message
                // is defined in a parent MessageSource but resolvable arguments
                // are defined in the child MessageSource.
                //处理参数
                argsToUse = resolveArguments(args, locale);
    
                MessageFormat messageFormat = resolveCode(code, locale);
                if (messageFormat != null) {
                    synchronized (messageFormat) {
                        return messageFormat.format(argsToUse);
                    }
                }
            }
    
            // Check locale-independent common messages for the given message code.
            Properties commonMessages = getCommonMessages();
            if (commonMessages != null) {
                String commonMessage = commonMessages.getProperty(code);
                if (commonMessage != null) {
                    return formatMessage(commonMessage, args, locale);
                }
            }
    
            // Not found -> check parent, if any.
            return getMessageFromParent(code, argsToUse, locale);
        }

    5、对参数的处理,参数如果是MessageSourceResolvable类型,可以继续获取真实的参数,然后直接组成数组

        protected Object[] resolveArguments(Object[] args, Locale locale) {
            if (args == null) {
                return new Object[0];
            }
            List<Object> resolvedArgs = new ArrayList<Object>(args.length);
            //查找参数是否为MessageSourceResolvable的实例,如果是,继续获取,如果不是,直接组成数组
            for (Object arg : args) {
                if (arg instanceof MessageSourceResolvable) {
                    resolvedArgs.add(getMessage((MessageSourceResolvable) arg, locale));
                }
                else {
                    resolvedArgs.add(arg);
                }
            }
            return resolvedArgs.toArray(new Object[resolvedArgs.size()]);
        }

    6、配置文件解析过程为:

      a)从baseNames的set中获取数据,

      b)然后依次调用java的ResourceBundle进行解析

      c)从解析的结果获取对应code的值

    注:此处我们可以在AbstractResourceBasedMessageSource中找到此bean的注入参数主要有以下几个:basenameSet(文件列表set)、defaultEncoding(文件默认编码)、fallbackToSystemLocale(是否使用系统默认的编码)、cacheMillis(cache时间),

        protected MessageFormat resolveCode(String code, Locale locale) {
            Set<String> basenames = getBasenameSet();
            for (String basename : basenames) {
                ResourceBundle bundle = getResourceBundle(basename, locale);
                if (bundle != null) {
                    MessageFormat messageFormat = getMessageFormat(bundle, code, locale);
                    if (messageFormat != null) {
                        return messageFormat;
                    }
                }
            }
            return null;
        }

    7、调用getResourceBundle方法,生成ResourceBundle,这里有一个和预想不同的设置,如果缓存时间没有设置,默认为永久保存,如果设置了反而直接要重新获取

    protected ResourceBundle getResourceBundle(String basename, Locale locale) {
            //判断是否设定了缓存时间,默认-1为永久保存,如果大于0,直接冲洗获取?
            if (getCacheMillis() >= 0) {
                // Fresh ResourceBundle.getBundle call in order to let ResourceBundle
                // do its native caching, at the expense of more extensive lookup steps.
                return doGetBundle(basename, locale);
            }
            else {
                // Cache forever: prefer locale cache over repeated getBundle calls.
                synchronized (this.cachedResourceBundles) {
                    //缓存所在的cachedResourceBundles
                    Map<Locale, ResourceBundle> localeMap = this.cachedResourceBundles.get(basename);
                    if (localeMap != null) {
                        ResourceBundle bundle = localeMap.get(locale);
                        if (bundle != null) {
                            return bundle;
                        }
                    }
                    try {
                        ResourceBundle bundle = doGetBundle(basename, locale);
                        if (localeMap == null) {
                            localeMap = new HashMap<Locale, ResourceBundle>();
                            this.cachedResourceBundles.put(basename, localeMap);
                        }
                        localeMap.put(locale, bundle);
                        return bundle;
                    }
                    catch (MissingResourceException ex) {
                        if (logger.isWarnEnabled()) {
                            logger.warn("ResourceBundle [" + basename + "] not found for MessageSource: " + ex.getMessage());
                        }
                        // Assume bundle not found
                        // -> do NOT throw the exception to allow for checking parent message source.
                        return null;
                    }
                }
            }
        }

    8、调用java的方法,获取ResourceBundle,需要写入自定义的MessageSourceControl

        protected ResourceBundle doGetBundle(String basename, Locale locale) throws MissingResourceException {
            return ResourceBundle.getBundle(basename, locale, getBundleClassLoader(), new MessageSourceControl());
        }

    9、MessageSourceControl中判断是format是java.properties还是java.class,我们的程序是java.class,但是调用了newBundle,再次会回调回来,此时又变成了java.properties.

    a)根据basename和local拼装propoties文件拼装文件名称

    b)如果生成的文件名称不存在,此时或根据FallbackToSystemLocale决定是否使用系统字符集,也就是zh_CN

    public Locale getFallbackLocale(String baseName, Locale locale) {
    return (isFallbackToSystemLocale() ? super.getFallbackLocale(baseName, locale) : null);
    }

    b)生成流文件

    c)根据默认的defaultEncoding,解析流

    注:

    此处可以看到,如果缓存时间大于0,使用的jdk自带缓存

    10、生成MessageFormat

    protected MessageFormat getMessageFormat(ResourceBundle bundle, String code, Locale locale)
                throws MissingResourceException {
    
            synchronized (this.cachedBundleMessageFormats) {
                //判断缓存中是否有MessageFormat,如果有,直接读取,没有重新获取
                Map<String, Map<Locale, MessageFormat>> codeMap = this.cachedBundleMessageFormats.get(bundle);
                Map<Locale, MessageFormat> localeMap = null;
                if (codeMap != null) {
                    localeMap = codeMap.get(code);
                    if (localeMap != null) {
                        MessageFormat result = localeMap.get(locale);
                        if (result != null) {
                            return result;
                        }
                    }
                }
    
                String msg = getStringOrNull(bundle, code);
                if (msg != null) {
                    if (codeMap == null) {
                        codeMap = new HashMap<String, Map<Locale, MessageFormat>>();
                        this.cachedBundleMessageFormats.put(bundle, codeMap);
                    }
                    if (localeMap == null) {
                        localeMap = new HashMap<Locale, MessageFormat>();
                        codeMap.put(code, localeMap);
                    }
                    MessageFormat result = createMessageFormat(msg, locale);
                    localeMap.put(locale, result);
                    return result;
                }
    
                return null;
            }
        }

    11、跳转回第四步,我们已经完成了整个返回信息的生成

    三、总结

    看完了源码,许多问题我们可以解决掉:

    1、messageSource使用的默认配置文件通过一个baseNames的set进行注入

    2、通常教程中说的中文乱码问题,spring提供了defaultEncoding设置,在解析流的时候可以按照设置的字符集进行解析

    3、我们设置了日语,却采用zh_CN进行解析,是因为默认使用了fallbackToSystemLocale

    4、messageSource默认使用自身的永久缓存,也可以设置,使用jdk的缓存

    5、如果没有参数,会默认执行resolveCodeWithoutArguments,不需要先转化为MessageFormat

    6、参数类型可以为MessageSourceResolvable类型

    修改bean的配置

            <bean id="messageSource"
                class="org.springframework.context.support.ResourceBundleMessageSource">
                <property name="basenames">
                    <list>
                        <value>messages</value>
                    </list>
                </property>
                <property name="defaultEncoding" value="utf-8" />
                <property name="fallbackToSystemLocale" value="false" />
                <property name="cacheMillis" value="3600" />
            </bean>

    2、将messages.zh_CN.properties改为如下

    info={0}去上学
    name=zhangsan

    3.修改测试程序,我们将参数改为MessageSourceResolvable类型,这个可以从高配置文件读取,如果没读取到可以使用默认

    public class Test {
        public static void main(String[] args) {
    
            MessageSourceResolvable resolvable=new DefaultMessageSourceResolvable(new String[]{"name"},"lisi");
            ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
            String info1=context.getMessage("info", new MessageSourceResolvable[]{resolvable}, Locale.getDefault());
            String info2=context.getMessage("info", new String[]{"张三"}, Locale.ENGLISH);
            
            String info3=context.getMessage("info", new MessageSourceResolvable[]{resolvable}, Locale.JAPAN);
    
            System.out.println("info1="+info1);
            System.out.println("info2="+info2);
            System.out.println("info3="+info3);
        }
    }

    4、然后执行测试程序,结果为

    info1=zhangsan去上学
    info2=张三 go to school
    info3=lisi hehehe

    可以看到:

    日文的已经转向系统默认设置

    参数为MessageSourceResolvable可以从配置文件读取,也可以使用默认值(此方法用处不明,很少看到这么用的)

  • 相关阅读:
    yaml简单模板
    goland之基础使用 X
    redis之性能优化 X
    定时任务管理之qinglong X
    golang数据库操作之gorm X
    低代码平台汇总 X
    kafka之介绍 X
    ClickHouse之基础 X
    gitlab安装与基本使用 X
    ClickHouse之物化MySQL X
  • 原文地址:https://www.cnblogs.com/jyyzzjl/p/5474088.html
Copyright © 2020-2023  润新知