[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可以從配置文件讀取,也可以使用默認值(此方法用處不明,很少看到這么用的)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM