Spring之国际化信息MessageSource源码阅读

MessageSource架构图

Spring之国际化信息MessageSource源码阅读_第1张图片
1. MessageSource:抽象化的消息接口
2. HierarchicalMessageSource:分层的消息源接口,可获取父消息源
3. MessageSourceSupport:帮助消息源解析的抽象类,通过指定“消息格式化组件MessageFormat”格式化消息。
4. DelegatingMessageSource: 消息源解析委派类(用户未指定时,SpringContext默认使用当前类),功能比较简单:将字符串和参数数组格式化为一个消息字符串
5. AbstractMessageSource:支持‘配置文件’的方式国际化资源 的 抽象类,内部提供一个与区域设置无关的公共消息配置文件,消息代码为关键字。
6. StaticMessageSource:主要用于程序测试,它允许通过编程的方式提供国际化信息
7. ResourceBundleMessageSource:该实现类允许用户通过beanName指定一个资源名(包括类路径的全限定资源名),或通过beanNames指定一组资源名。不同的区域获取加载资源文件,达到国际化信息的目的。
8. ReloadableResourceBundleMessageSource:同ResourceBundleMessageSource区别(spring3.2):
1)加载资源类型及方式:
ResourceBundleMessageSource 依托JDK自带ResourceBundle加载资源,支持绝对路径和工程路径,支持文件为.class文件和.properties。
ReloadResourceBundleMessageSource依托spring的ResourceLoader加载Resource资源,功能更加强大,同时支持.properties和.xml文件。
2)缓存时间:
ResourceBundleMessageSource主要利用ResourceBundle.Control 实现简单的自动重载。
ReloadResourceBundleMessageSource每次加载资源都会记录每个资源的加载时间点,在缓存资源过期后会再次比较文件的修改时间,如果不变则不需要重新加载,同时刷新本次加载时间点。
3)编码方式:
ResourceBundleMessageSource可以统一指定默认的文件编码方式
ReloadResourceBundleMessageSource不仅可以指定统一的默认编码方式,也同时支持为每个文件单独制定编码方式

spring中初始化MessageSource组件

//springContext国际化资源信息初始化。这里的messageSource主要是将这个Bean定义的信息资源加载为容器级的国际化信息资源。
public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext, DisposableBean {

    public static final String MESSAGE_SOURCE_BEAN_NAME = "messageSource";

    /** 实际委托的消息源解析对象 */
    private MessageSource messageSource;

    //======省略一段代码========/
    public void refresh() throws BeansException, IllegalStateException {

        //======省略一段代码========/

        // 初始化此上下文的消息源。
        initMessageSource();

        // Initialize event multicaster for this context.
        initApplicationEventMulticaster();

        //======省略一段代码========/
    }

    //======省略一段代码========/


    /**
     * 初始化此上下文的消息源。
     * 如果没有在此上下文中定义,请使用父级。
     */
    protected void initMessageSource() {
        //获取工厂
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        //先找工厂中messageSource是否有对应的实例(注册过)
        if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
            this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
            //messageSource是否有父消息源
            if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
                HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
                if (hms.getParentMessageSource() == null) {
                    // 父MessageSource未注册,则设置messageSource的parentMessageSource为“父上下文的‘MessageSource’ ”。
                    hms.setParentMessageSource(getInternalParentMessageSource());
                }
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Using MessageSource [" + this.messageSource + "]");
            }
        }
        else {//没有对应的实例,则自己初始化一个
            // 实例化一个spring默认实现消息源对象
            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 + "]");
            }
        }
    }
    //获取父上下文的消息源
    protected MessageSource getInternalParentMessageSource() {
        return (getParent() instanceof AbstractApplicationContext) ?
            ((AbstractApplicationContext) getParent()).messageSource : getParent();
    }
}

各个类源码阅读

MessageSource接口

public interface MessageSource {

    /**
     * 尝试解决消息。 如果没有找到消息,返回默认消息。
     */
    String getMessage(String code, Object[] args, String defaultMessage, Locale locale);

    /**
     * 尝试解决消息。 如果无法找到消息,则视为错误。
     */
    String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;

    /**
     * 尝试使用传入的{@code MessageSourceResolvable}参数中包含的所有属性来解析消息。
     * 

NOTE: 我们必须在此方法上抛出{@code NoSuchMessageException},因为在调用此方法时,我们无法确定可解析的{@code defaultMessage}属性是否为空。 */ String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException; }

MessageSourceResolvable解析消息要素的包装接口和类

//解析消息要素的包装类:code、Arguments、默认消息
public interface MessageSourceResolvable {

    /**
     * 返回用于解决此消息的代码,按照他们应该尝试的顺序。 因此,最后一个代码将是默认代码。
     * @return a String array of codes which are associated with this message
     */
    String[] getCodes();

    /**
     * 返回要用于解析此消息的参数数组。
     * @return an array of objects to be used as parameters to replace
     * placeholders within the message text
     * @see java.text.MessageFormat
     */
    Object[] getArguments();

    /**
     * 返回要用于解析此消息的默认消息。
     * @return the default message, or {@code null} if no default
     */
    String getDefaultMessage();

}

//spring默认实现的 解析消息要素的包装类
public class DefaultMessageSourceResolvable implements MessageSourceResolvable, Serializable {

    private final String[] codes;

    private final Object[] arguments;

    private final String defaultMessage;


    //构造方法
    public DefaultMessageSourceResolvable(String code) {
        this(new String[] {code}, null, null);
    }
    //构造方法
    public DefaultMessageSourceResolvable(String[] codes) {
        this(codes, null, null);
    }

    //构造方法
    public DefaultMessageSourceResolvable(String[] codes, String defaultMessage) {
        this(codes, null, defaultMessage);
    }
    //构造方法
    public DefaultMessageSourceResolvable(String[] codes, Object[] arguments) {
        this(codes, arguments, null);
    }

    //构造方法
    public DefaultMessageSourceResolvable(String[] codes, Object[] arguments, String defaultMessage) {
        this.codes = codes;
        this.arguments = arguments;
        this.defaultMessage = defaultMessage;
    }
    //构造方法
    public DefaultMessageSourceResolvable(MessageSourceResolvable resolvable) {
        this(resolvable.getCodes(), resolvable.getArguments(), resolvable.getDefaultMessage());
    }


    public String[] getCodes() {
        return this.codes;
    }

    /**
     * 返回此可解析的默认代码,即代码数组中的最后一个代码。
     */
    public String getCode() {
        return (this.codes != null && this.codes.length > 0) ? this.codes[this.codes.length - 1] : null;
    }

    public Object[] getArguments() {
        return this.arguments;
    }

    public String getDefaultMessage() {
        return this.defaultMessage;
    }


    /**
     * 为此MessageSourceResolvable构建默认的String表示形式:包括代码,参数和默认消息。
     */
    protected final String resolvableToString() {
        StringBuilder result = new StringBuilder();
        result.append("codes [").append(StringUtils.arrayToDelimitedString(this.codes, ","));
        result.append("]; arguments [" + StringUtils.arrayToDelimitedString(this.arguments, ","));
        result.append("]; default message [").append(this.defaultMessage).append(']');
        return result.toString();
    }

    /**默认实现公开了此MessageSourceResolvable的属性。要在更具体的子类中被覆盖,可能通过{@code resolvableToString()}包含可解析的内容。
     * @see #resolvableToString()
     */
    @Override
    public String toString() {
        return getClass().getName() + ": " + resolvableToString();
    }


    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof MessageSourceResolvable)) {
            return false;
        }
        MessageSourceResolvable otherResolvable = (MessageSourceResolvable) other;
        return ObjectUtils.nullSafeEquals(getCodes(), otherResolvable.getCodes()) &&
                ObjectUtils.nullSafeEquals(getArguments(), otherResolvable.getArguments()) &&
                ObjectUtils.nullSafeEquals(getDefaultMessage(), otherResolvable.getDefaultMessage());
    }

    @Override
    public int hashCode() {
        int hashCode = ObjectUtils.nullSafeHashCode(getCodes());
        hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(getArguments());
        hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(getDefaultMessage());
        return hashCode;
    }

}

HierarchicalMessageSource消息源分层接口

public interface HierarchicalMessageSource extends MessageSource {

    /**
     * 设置将用于尝试解决此对象无法解析的消息的父级。
     * @param parent 将用于解析此对象无法解析的邮件的父MessageSource。 可能是{@code null},在这种情况下不需要进一步的解决。
     */
    void setParentMessageSource(MessageSource parent);

    /**
     * 返回此MessageSource的父级,否则返回{@code null}。
     */
    MessageSource getParentMessageSource();

}

MessageSourceSupport

//用于支撑消息源解析的抽象类
public abstract class MessageSourceSupport {
    //默认’消息格式组件‘
    private static final MessageFormat INVALID_MESSAGE_FORMAT = new MessageFormat("");

    /** Logger available to subclasses */
    protected final Log logger = LogFactory.getLog(getClass());
    //是否始终应用’消息格式组件‘,解析没有参数的消息。
    private boolean alwaysUseMessageFormat = false;


    //缓存来保存已解决消息msg的’消息格式组件‘。 用于传入的默认消息(如:AbstractMessageSource中的commonMessages中的消息)。 
    private final Map> messageFormatsPerMessage =
            new HashMap>();


    /**
     * 设置是否始终应用’消息格式组件‘,解析没有参数的消息。
     * 

例如:,MessageFormat希望单引号被转义为"''"。 如果您的消息文本全部用这样的转义编写, * 即使没有定义参数占位符,您需要将此标志设置为“true”。 * 否则,只有具有实际参数的消息文本应该用MessageFormat转义来编写。 * @see java.text.MessageFormat */ public void setAlwaysUseMessageFormat(boolean alwaysUseMessageFormat) { this.alwaysUseMessageFormat = alwaysUseMessageFormat; } /** * 返回是否始终应用’消息格式组件‘,解析没有参数的消息。 */ protected boolean isAlwaysUseMessageFormat() { return this.alwaysUseMessageFormat; } //渲染给定的默认消息字符串。 protected String renderDefaultMessage(String defaultMessage, Object[] args, Locale locale) { return formatMessage(defaultMessage, args, locale); } //渲染给定的消息字符串。 protected String formatMessage(String msg, Object[] args, Locale locale) { if (msg == null || (!this.alwaysUseMessageFormat && ObjectUtils.isEmpty(args))) { return msg; } MessageFormat messageFormat = null; synchronized (this.messageFormatsPerMessage) { //防止并发操作 //先尝试取缓存’消息格式组件‘ Map messageFormatsPerLocale = this.messageFormatsPerMessage.get(msg); if (messageFormatsPerLocale != null) { messageFormat = messageFormatsPerLocale.get(locale); } else {//缓存为空,设置初始化空map值 messageFormatsPerLocale = new HashMap(); this.messageFormatsPerMessage.put(msg, messageFormatsPerLocale); } //’消息格式组件‘为空,则初始化一个messageFormat if (messageFormat == null) { try { messageFormat = createMessageFormat(msg, locale); } catch (IllegalArgumentException ex) { // 如果alwaysUseMessageFormat为true,即抛错 if (this.alwaysUseMessageFormat) { throw ex; } // 如果格式未被强制执行,则默认继续处理原始消息 messageFormat = INVALID_MESSAGE_FORMAT; } //缓存当前消息的’消息格式组件‘ messageFormatsPerLocale.put(locale, messageFormat); } } //消息解析对象为默认消息解析器,直接返回msg if (messageFormat == INVALID_MESSAGE_FORMAT) { return msg; } //使用messageFormat解析解析消息 synchronized (messageFormat) { return messageFormat.format(resolveArguments(args, locale)); } } //为给定的消息和区域设置创建一个MessageFormat。 protected MessageFormat createMessageFormat(String msg, Locale locale) { return new MessageFormat((msg != null ? msg : ""), locale); } //子类可以覆盖,否则只是按原样返回给定的参数数组 protected Object[] resolveArguments(Object[] args, Locale locale) { return args; } }

DelegatingMessageSource消息源解析委派类

//第一部分:消息源解析委派类(用户未指定时,SpringContext默认使用当前类)
public class DelegatingMessageSource extends MessageSourceSupport implements HierarchicalMessageSource {
    //父消息解析源
    private MessageSource parentMessageSource;


    public void setParentMessageSource(MessageSource parent) {
        this.parentMessageSource = parent;
    }

    public MessageSource getParentMessageSource() {
        return this.parentMessageSource;
    }

    //解析消息,父消息解析源不为null时,则采用父消息源解析消息,否则有自身解析
    public String getMessage(String code, Object[] args, String defaultMessage, Locale locale) {
        if (this.parentMessageSource != null) { 
            return this.parentMessageSource.getMessage(code, args, defaultMessage, locale);
        }
        else {
            return renderDefaultMessage(defaultMessage, args, locale);
        }
    }
    //解析消息,父消息解析源不为null时,则采用父消息源解析消息,否则抛错
    public String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException {
        if (this.parentMessageSource != null) {
            return this.parentMessageSource.getMessage(code, args, locale);
        }
        else {
            throw new NoSuchMessageException(code, locale);
        }
    }
    //解析消息,父消息解析源不为null时,则采用父消息源解析消息,否则有自身解析
    public String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
        if (this.parentMessageSource != null) {
            return this.parentMessageSource.getMessage(resolvable, locale);
        }
        else {
            if (resolvable.getDefaultMessage() != null) {
                return renderDefaultMessage(resolvable.getDefaultMessage(), resolvable.getArguments(), locale);
            }
            String[] codes = resolvable.getCodes();
            String code = (codes != null && codes.length > 0 ? codes[0] : null);
            throw new NoSuchMessageException(code, locale);
        }
    }

}

AbstractMessageSource抽象类

//第二部分:spring支持‘配置文件’的方式国际化资源 的 抽象类
public abstract class AbstractMessageSource extends MessageSourceSupport implements HierarchicalMessageSource {
    //父消息源
    private MessageSource parentMessageSource;

    //与区域设置无关的公共消息,消息代码为关键字
    // 
    //   
    //       
    //           classpath*:application.properties
    //       
    //   
    //
    private Properties commonMessages;

    //是否使用消息代码作为默认消息,而不是抛出NoSuchMessageException。 适用于开发和调试。
    private boolean useCodeAsDefaultMessage = false;


    public void setParentMessageSource(MessageSource parent) {
        this.parentMessageSource = parent;
    }

    public MessageSource getParentMessageSource() {
        return this.parentMessageSource;
    }

    /**
     * Specify locale-independent common messages, with the message code as key
     * and the full message String (may contain argument placeholders) as value.
     * 

May also link to an externally defined Properties object, e.g. defined * through a {@link org.springframework.beans.factory.config.PropertiesFactoryBean}. */ public void setCommonMessages(Properties commonMessages) { this.commonMessages = commonMessages; } /** * Return a Properties object defining locale-independent common messages, if any. */ protected Properties getCommonMessages() { return this.commonMessages; } //设置是否使用消息代码作为默认消息,而不是抛出NoSuchMessageException。 适用于开发和调试。 //默认值为“false”。 public void setUseCodeAsDefaultMessage(boolean useCodeAsDefaultMessage) { this.useCodeAsDefaultMessage = useCodeAsDefaultMessage; } protected boolean isUseCodeAsDefaultMessage() { return this.useCodeAsDefaultMessage; } public final String getMessage(String code, Object[] args, String defaultMessage, Locale locale) { String msg = getMessageInternal(code, args, locale); if (msg != null) { return msg; } if (defaultMessage == null) { String fallback = getDefaultMessage(code); if (fallback != null) { return fallback; } } return renderDefaultMessage(defaultMessage, args, locale); } public final String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException { String msg = getMessageInternal(code, args, locale); if (msg != null) { return msg; } String fallback = getDefaultMessage(code); if (fallback != null) { return fallback; } throw new NoSuchMessageException(code, locale); } public final String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException { String[] codes = resolvable.getCodes(); if (codes == null) { codes = new String[0]; } for (String code : codes) { String msg = getMessageInternal(code, resolvable.getArguments(), locale); if (msg != null) { return msg; } } String defaultMessage = resolvable.getDefaultMessage(); if (defaultMessage != null) { return renderDefaultMessage(defaultMessage, resolvable.getArguments(), locale); } if (codes.length > 0) { String fallback = getDefaultMessage(codes[0]); if (fallback != null) { return fallback; } } throw new NoSuchMessageException(codes.length > 0 ? codes[codes.length - 1] : null, locale); } //将给定的代码和参数解析为给定的区域设置中的消息,如果没有找到返回{@code null}。 protected String getMessageInternal(String code, Object[] args, Locale locale) { if (code == null) { return null; } if (locale == null) { locale = Locale.getDefault(); } Object[] argsToUse = args; if (!isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args)) { //解析不带参数数组的消息 String message = resolveCodeWithoutArguments(code, locale); if (message != null) { return message; } } else { //解析参数数组 argsToUse = resolveArguments(args, locale); //获取’消息格式化组件‘ MessageFormat messageFormat = resolveCode(code, locale); if (messageFormat != null) { synchronized (messageFormat) { return messageFormat.format(argsToUse); } } } // 检查指定消息代码的区域设置独立公共消息。获取配置文件中的消息主体 Properties commonMessages = getCommonMessages(); if (commonMessages != null) { String commonMessage = commonMessages.getProperty(code); if (commonMessage != null) { return formatMessage(commonMessage, args, locale); } } //未找到,则尝试使用父消息源解析消息(如果存在) return getMessageFromParent(code, argsToUse, locale); } //尝试从父MessageSource(如果有)检索给定的消息。 protected String getMessageFromParent(String code, Object[] args, Locale locale) { MessageSource parent = getParentMessageSource(); if (parent != null) { if (parent instanceof AbstractMessageSource) { // Call internal method to avoid getting the default code back // in case of "useCodeAsDefaultMessage" being activated. return ((AbstractMessageSource) parent).getMessageInternal(code, args, locale); } else { // Check parent MessageSource, returning null if not found there. return parent.getMessage(code, args, null, locale); } } // Not found in parent either. return null; } //返回默认消息。 protected String getDefaultMessage(String code) { if (isUseCodeAsDefaultMessage()) { //返回给定代码作为默认消息。 return code; } return null; } //通过给定的参数数组搜索,找到任何MessageSourceResolvable对象并解析它们。 @Override protected Object[] resolveArguments(Object[] args, Locale locale) { if (args == null) { return new Object[0]; } List resolvedArgs = new ArrayList(args.length); 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()]); } //解析不带参数的消息 protected String resolveCodeWithoutArguments(String code, Locale locale) { MessageFormat messageFormat = resolveCode(code, locale); if (messageFormat != null) { synchronized (messageFormat) { return messageFormat.format(new Object[0]); } } return null; } //返回”消息格式化组件“(子类必须实现此方法来解决消息) protected abstract MessageFormat resolveCode(String code, Locale locale); }

StaticMessageSource


//实现类一:主要用于程序测试,它允许通过编程的方式提供国际化信息。
public class StaticMessageSource extends AbstractMessageSource {

    //从'code + locale'键映射到消息字符串
    private final Map messages = new HashMap();

    private final Map cachedMessageFormats = new HashMap();


    @Override
    protected String resolveCodeWithoutArguments(String code, Locale locale) {
        return this.messages.get(code + "_" + locale.toString());
    }

    @Override
    protected MessageFormat resolveCode(String code, Locale locale) {
        String key = code + "_" + locale.toString();
        //如果为手动关联的消息串,直接返回null
        String msg = this.messages.get(key);
        if (msg == null) {
            return null;
        }
        synchronized (this.cachedMessageFormats) {
            MessageFormat messageFormat = this.cachedMessageFormats.get(key);
            if (messageFormat == null) {
                //构建“消息格式组件”
                messageFormat = createMessageFormat(msg, locale);
                this.cachedMessageFormats.put(key, messageFormat);
            }
            return messageFormat;
        }
    }

    //将给定的消息与给定的代码相关联。
    public void addMessage(String code, Locale locale, String msg) {
        Assert.notNull(code, "Code must not be null");
        Assert.notNull(locale, "Locale must not be null");
        Assert.notNull(msg, "Message must not be null");
        this.messages.put(code + "_" + locale.toString(), msg);
        if (logger.isDebugEnabled()) {
            logger.debug("Added message [" + msg + "] for code [" + code + "] and Locale [" + locale + "]");
        }
    }

    //批量将给定的消息与给定的代码相关联。
    public void addMessages(Map messages, Locale locale) {
        Assert.notNull(messages, "Messages Map must not be null");
        for (Map.Entry entry : messages.entrySet()) {
            addMessage(entry.getKey(), locale, entry.getValue());
        }
    }


    @Override
    public String toString() {
        return getClass().getName() + ": " + this.messages;
    }

}

ResourceBundleMessageSource

//实现类二:该实现类允许用户通过beanName指定一个资源名(包括类路径的全限定资源名),或通过beanNames指定一组资源名。
//使用方式:
//  
//   
//       
//       com/baobaotao/i18n/fmt_resource  
//       
//    
//  
public class ResourceBundleMessageSource extends AbstractMessageSource implements BeanClassLoaderAware {
    //资源文件列表(包括类路径的全限定资源名)
    private String[] basenames = new String[0];
    //用于解析资源束文件的默认字符集。
    private String defaultEncoding;
    //是否使用系统默认的编码
    private boolean fallbackToSystemLocale = true;
    //缓存时间
    private long cacheMillis = -1;
    //加载绑定资源文件的类加载器
    private ClassLoader bundleClassLoader;

    private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();

    //缓存code和区域所对应 资源包ResourceBundles。
    private final Map> cachedResourceBundles =
            new HashMap>();

    //缓存ResourceBundle和code及local所对应的MessageFormat
    private final Map>> cachedBundleMessageFormats =
            new HashMap>>();

    //设置资源文件
    public void setBasename(String basename) {
        setBasenames(basename);
    }
    //批量设置资源文件
    public void setBasenames(String... basenames) {
        if (basenames != null) {
            this.basenames = new String[basenames.length];
            for (int i = 0; i < basenames.length; i++) {
                String basename = basenames[i];
                Assert.hasText(basename, "Basename must not be empty");
                this.basenames[i] = basename.trim();
            }
        }
        else {
            this.basenames = new String[0];
        }
    }

    //设置用于解析绑定的资源文件的默认字符集。
    public void setDefaultEncoding(String defaultEncoding) {
        this.defaultEncoding = defaultEncoding;
    }

    //设置是否返回到系统区域设置,如果没有找到特定语言环境的文件。 
    // 默认为“true”; 如果这是关闭的,唯一的后备将是默认文件(例如,basename“messages”的“messages.properties”)。
    public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) {
        this.fallbackToSystemLocale = fallbackToSystemLocale;
    }


    //设置缓存加载的绑定的资源文件的秒数。
    public void setCacheSeconds(int cacheSeconds) {
        this.cacheMillis = (cacheSeconds * 1000);
    }


    //设置记载绑定资源文件的类加载器
    public void setBundleClassLoader(ClassLoader classLoader) {
        this.bundleClassLoader = classLoader;
    }

    protected ClassLoader getBundleClassLoader() {
        return (this.bundleClassLoader != null ? this.bundleClassLoader : this.beanClassLoader);
    }

    //设置bean类加载器
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.beanClassLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
    }


    /**
     * 将给定的消息代码解析为已注册资源包中的key,按原样返回捆绑包中的值(不使用MessageFormat解析)。
     */
    @Override
    protected String resolveCodeWithoutArguments(String code, Locale locale) {
        String result = null;
        for (int i = 0; result == null && i < this.basenames.length; i++) {
            ResourceBundle bundle = getResourceBundle(this.basenames[i], locale);
            if (bundle != null) {
                result = getStringOrNull(bundle, code);
            }
        }
        return result;
    }

    /**
     * 将给定的消息代码解析为注册资源包中的key,每个消息代码使用缓存的MessageFormat实例。
     */
    @Override
    protected MessageFormat resolveCode(String code, Locale locale) {
        MessageFormat messageFormat = null;
        for (int i = 0; messageFormat == null && i < this.basenames.length; i++) {
            ResourceBundle bundle = getResourceBundle(this.basenames[i], locale);
            if (bundle != null) {
                messageFormat = getMessageFormat(bundle, code, locale);
            }
        }
        return messageFormat;
    }


    //为给定的basename和代码返回一个ResourceBundle,从缓存中提取已生成的MessageFormats。
    protected ResourceBundle getResourceBundle(String basename, Locale locale) {
        if (this.cacheMillis >= 0) { 
            // 新建ResourceBundle.getBundle调用,以使ResourceBundle执行其本地缓存,而不必花费更广泛的查找步骤。
            return doGetBundle(basename, locale);
        }
        else {
            // 永久缓存:在重复的getBundle调用中优先使用语言环境缓存。
            synchronized (this.cachedResourceBundles) {
                //先获取缓存
                Map 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();
                        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;
                }
            }
        }
    }

    //获取给定basename和locale设置的资源包。
    protected ResourceBundle doGetBundle(String basename, Locale locale) throws MissingResourceException {
        if ((this.defaultEncoding != null && !"ISO-8859-1".equals(this.defaultEncoding)) ||
                !this.fallbackToSystemLocale || this.cacheMillis >= 0) {
            //jdk版本不能小于16
            if (JdkVersion.getMajorJavaVersion() < JdkVersion.JAVA_16) {
                throw new IllegalStateException("Cannot use 'defaultEncoding', 'fallbackToSystemLocale' and " +
                        "'cacheSeconds' on the standard ResourceBundleMessageSource when running on Java 5. " +
                        "Consider using ReloadableResourceBundleMessageSource instead.");
            }
            return new ControlBasedResourceBundleFactory().getBundle(basename, locale);
        }
        else {
            // Good old standard call...
            return ResourceBundle.getBundle(basename, locale, getBundleClassLoader());
        }
    }

    //为给定的包和代码返回一个MessageFormat,从缓存中提取已生成的MessageFormats。
    protected MessageFormat getMessageFormat(ResourceBundle bundle, String code, Locale locale)
            throws MissingResourceException {

        synchronized (this.cachedBundleMessageFormats) {
            //先走缓存
            Map> codeMap = this.cachedBundleMessageFormats.get(bundle);
            Map localeMap = null;
            if (codeMap != null) {
                localeMap = codeMap.get(code);
                if (localeMap != null) {
                    MessageFormat result = localeMap.get(locale);
                    if (result != null) {
                        return result;
                    }
                }
            }
            //缓存为空的话,执行下面步骤
            //获取资源包中指定key所对应的值
            String msg = getStringOrNull(bundle, code);
            if (msg != null) {
                //放入缓存
                if (codeMap == null) {
                    codeMap = new HashMap>();
                    this.cachedBundleMessageFormats.put(bundle, codeMap);
                }
                if (localeMap == null) {
                    localeMap = new HashMap();
                    codeMap.put(code, localeMap);
                }
                MessageFormat result = createMessageFormat(msg, locale);
                localeMap.put(locale, result);
                return result;
            }

            return null;
        }
    }
    //获取资源包中指定key所对应的值
    private String getStringOrNull(ResourceBundle bundle, String key) {
        try {
            return bundle.getString(key);
        }
        catch (MissingResourceException ex) {
            // Assume key not found
            // -> do NOT throw the exception to allow for checking parent message source.
            return null;
        }
    }

    /**
     * Show the configuration of this MessageSource.
     */
    @Override
    public String toString() {
        return getClass().getName() + ": basenames=[" +
                StringUtils.arrayToCommaDelimitedString(this.basenames) + "]";
    }


    /**
     * Factory indirection for runtime isolation of the optional dependencv on
     * Java 6's Control class.
     * @see ResourceBundle#getBundle(String, java.util.Locale, ClassLoader, java.util.ResourceBundle.Control)
     * @see MessageSourceControl
     */
    private class ControlBasedResourceBundleFactory {

        public ResourceBundle getBundle(String basename, Locale locale) {
            return ResourceBundle.getBundle(basename, locale, getBundleClassLoader(), new MessageSourceControl());
        }
    }


    /**
     * Custom implementation of Java 6's {@code ResourceBundle.Control},
     * adding support for custom file encodings, deactivating the fallback to the
     * system locale and activating ResourceBundle's native cache, if desired.
     */
    private class MessageSourceControl extends ResourceBundle.Control {

        @Override
        public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
                throws IllegalAccessException, InstantiationException, IOException {
            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 stream;
                try {
                    stream = AccessController.doPrivileged(
                            new PrivilegedExceptionAction() {
                                public InputStream run() throws IOException {
                                    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();
                                            }
                                        }
                                    }
                                    else {
                                        is = classLoader.getResourceAsStream(resourceName);
                                    }
                                    return is;
                                }
                            });
                }
                catch (PrivilegedActionException ex) {
                    throw (IOException) ex.getException();
                }
                if (stream != null) {
                    try {
                        return (defaultEncoding != null ?
                                new PropertyResourceBundle(new InputStreamReader(stream, defaultEncoding)) :
                                new PropertyResourceBundle(stream));
                    }
                    finally {
                        stream.close();
                    }
                }
                else {
                    return null;
                }
            }
            else {
                return super.newBundle(baseName, locale, format, loader, reload);
            }
        }

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

        @Override
        public long getTimeToLive(String baseName, Locale locale) {
            return (cacheMillis >= 0 ? cacheMillis : super.getTimeToLive(baseName, locale));
        }

        @Override
        public boolean needsReload(String baseName, Locale locale, String format, ClassLoader loader, ResourceBundle bundle, long loadTime) {
            if (super.needsReload(baseName, locale, format, loader, bundle, loadTime)) {
                cachedBundleMessageFormats.remove(bundle);
                return true;
            }
            else {
                return false;
            }
        }
    }

}

ReloadableResourceBundleMessageSource


//实现类三:该实现类允许用户通过beanName指定一个资源名(包括类路径的全限定资源名),或通过beanNames指定一组资源名。
//使用方式:
//  
//   
//       
//       com/baobaotao/i18n/fmt_resource  
//       
//    
//    
//     
//  
public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
        implements ResourceLoaderAware {

    private static final String PROPERTIES_SUFFIX = ".properties";

    private static final String XML_SUFFIX = ".xml";

    //资源文件包名
    private String[] basenames = new String[0];
    //默认编码方式
    private String defaultEncoding;
    //不同文件设置不同的编码方式
    private Properties fileEncodings;
    //使用系统区域设置
    private boolean fallbackToSystemLocale = true;
    //缓存时间
    private long cacheMillis = -1;

    private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();

    private ResourceLoader resourceLoader = new DefaultResourceLoader();

    /** 文件名-区域 所对应的配置文件名 */
    private final Map>> cachedFilenames =
            new HashMap>>();

    /** 缓存来保存每个文件名已经加载的属性 */
    private final Map cachedProperties = new HashMap();

    /** 缓存以保存每个区域的合并加载属性*/
    private final Map cachedMergedProperties = new HashMap();



    public void setBasename(String basename) {
        setBasenames(basename);
    }
    public void setBasenames(String... basenames) {
        if (basenames != null) {
            this.basenames = new String[basenames.length];
            for (int i = 0; i < basenames.length; i++) {
                String basename = basenames[i];
                Assert.hasText(basename, "Basename must not be empty");
                this.basenames[i] = basename.trim();
            }
        }
        else {
            this.basenames = new String[0];
        }
    }


    public void setDefaultEncoding(String defaultEncoding) {
        this.defaultEncoding = defaultEncoding;
    }

    public void setFileEncodings(Properties fileEncodings) {
        this.fileEncodings = fileEncodings;
    }

    public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) {
        this.fallbackToSystemLocale = fallbackToSystemLocale;
    }
    //过期时间设置
    public void setCacheSeconds(int cacheSeconds) {
        this.cacheMillis = (cacheSeconds * 1000);
    }

    //设置PropertiesPersister用于解析属性文件。
    public void setPropertiesPersister(PropertiesPersister propertiesPersister) {
        this.propertiesPersister =
                (propertiesPersister != null ? propertiesPersister : new DefaultPropertiesPersister());
    }

    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader());
    }


    /**
     * 将给定的消息代码解析为检索到的包文件中的key,按原样返回包中找到的值(不使用MessageFormat解析)。
     */
    @Override
    protected String resolveCodeWithoutArguments(String code, Locale locale) {
        if (this.cacheMillis < 0) {
            PropertiesHolder propHolder = getMergedProperties(locale);
            String result = propHolder.getProperty(code);
            if (result != null) {
                return result;
            }
        }
        else {
            for (String basename : this.basenames) {
                List filenames = calculateAllFilenames(basename, locale);
                for (String filename : filenames) {
                    PropertiesHolder propHolder = getProperties(filename);
                    String result = propHolder.getProperty(code);
                    if (result != null) {
                        return result;
                    }
                }
            }
        }
        return null;
    }

    /**
     * 将给定的消息代码解析为检索到的包文件中的key,每个消息代码使用缓存的MessageFormat实例。
     */
    @Override
    protected MessageFormat resolveCode(String code, Locale locale) {
        if (this.cacheMillis < 0) { 
            PropertiesHolder propHolder = getMergedProperties(locale);
            MessageFormat result = propHolder.getMessageFormat(code, locale);
            if (result != null) {
                return result;
            }
        }
        else {
            //遍历资源包,生成key所对应的messageFormat
            for (String basename : this.basenames) {
                List filenames = calculateAllFilenames(basename, locale);
                for (String filename : filenames) {
                    PropertiesHolder propHolder = getProperties(filename);
                    MessageFormat result = propHolder.getMessageFormat(code, locale);
                    if (result != null) {
                        return result;
                    }
                }
            }
        }
        return null;
    }


    /**
     * 获取locale所对应的持有properties对象
     */
    protected PropertiesHolder getMergedProperties(Locale locale) {
        synchronized (this.cachedMergedProperties) {
            //先走缓存
            PropertiesHolder mergedHolder = this.cachedMergedProperties.get(locale);
            if (mergedHolder != null) {
                return mergedHolder;
            }
            //创建一个新的properties对象
            Properties mergedProps = new Properties();
            //创建一个的持有properties的对象
            mergedHolder = new PropertiesHolder(mergedProps, -1);
            for (int i = this.basenames.length - 1; i >= 0; i--) {//遍历资源包名列表
                //获取该区域该资源包名所对应的资源文件列表
                List filenames = calculateAllFilenames(this.basenames[i], locale);
                for (int j = filenames.size() - 1; j >= 0; j--) {
                    String filename = (String) filenames.get(j);
                    PropertiesHolder propHolder = getProperties(filename);
                    if (propHolder.getProperties() != null) {
                        mergedProps.putAll(propHolder.getProperties());
                    }
                }
            }
            this.cachedMergedProperties.put(locale, mergedHolder);
            return mergedHolder;
        }
    }

    /**
     * 计算给定的捆绑包基础名称和区域设置的所有文件名。 将计算给定区域设置的文件名,系统区域设置(如果适用)和默认文件。
     * @param basename the basename of the bundle
     * @param locale the locale
     * @return the List of filenames to check
     * @see #setFallbackToSystemLocale
     * @see #calculateFilenamesForLocale
     */
    protected List calculateAllFilenames(String basename, Locale locale) {
        synchronized (this.cachedFilenames) {
            Map> localeMap = this.cachedFilenames.get(basename);
            if (localeMap != null) {
                List filenames = localeMap.get(locale);
                if (filenames != null) {
                    return filenames;
                }
            }
            List filenames = new ArrayList(7);
            filenames.addAll(calculateFilenamesForLocale(basename, locale));
            if (this.fallbackToSystemLocale && !locale.equals(Locale.getDefault())) {
                List fallbackFilenames = calculateFilenamesForLocale(basename, Locale.getDefault());
                for (String fallbackFilename : fallbackFilenames) {
                    if (!filenames.contains(fallbackFilename)) {
                        // Entry for fallback locale that isn't already in filenames list.
                        filenames.add(fallbackFilename);
                    }
                }
            }
            filenames.add(basename);
            if (localeMap != null) {
                localeMap.put(locale, filenames);
            }
            else {
                localeMap = new HashMap>();
                localeMap.put(locale, filenames);
                this.cachedFilenames.put(basename, localeMap);
            }
            return filenames;
        }
    }

    /**
     * Calculate the filenames for the given bundle basename and Locale,
     * appending language code, country code, and variant code.
     * E.g.: basename "messages", Locale "de_AT_oo" -> "messages_de_AT_OO",
     * "messages_de_AT", "messages_de".
     * 

Follows the rules defined by {@link java.util.Locale#toString()}. * @param basename the basename of the bundle * @param locale the locale * @return the List of filenames to check */ protected List calculateFilenamesForLocale(String basename, Locale locale) { List result = new ArrayList(3); String language = locale.getLanguage(); String country = locale.getCountry(); String variant = locale.getVariant(); StringBuilder temp = new StringBuilder(basename); temp.append('_'); if (language.length() > 0) { temp.append(language); result.add(0, temp.toString()); } temp.append('_'); if (country.length() > 0) { temp.append(country); result.add(0, temp.toString()); } if (variant.length() > 0 && (language.length() > 0 || country.length() > 0)) { temp.append('_').append(variant); result.add(0, temp.toString()); } return result; } //从缓存或新加载中获取给定文件名的PropertiesHolder。 protected PropertiesHolder getProperties(String filename) { synchronized (this.cachedProperties) { PropertiesHolder propHolder = this.cachedProperties.get(filename); //判断是否需要刷新 if (propHolder != null && (propHolder.getRefreshTimestamp() < 0 || propHolder.getRefreshTimestamp() > System.currentTimeMillis() - this.cacheMillis)) { // up to date return propHolder; } return refreshProperties(filename, propHolder); } } //刷新给定包文件名的PropertiesHolder。 protected PropertiesHolder refreshProperties(String filename, PropertiesHolder propHolder) { //记录刷新时间 long refreshTimestamp = (this.cacheMillis < 0 ? -1 : System.currentTimeMillis()); //加载资源文件所对应的resource对象 Resource resource = this.resourceLoader.getResource(filename + PROPERTIES_SUFFIX); if (!resource.exists()) { //如果resource不存在,则加载对应的xml文件 resource = this.resourceLoader.getResource(filename + XML_SUFFIX); } if (resource.exists()) { long fileTimestamp = -1; if (this.cacheMillis >= 0) { // 如果设置了缓存时间,文件的最后修改的时间戳将被读取。 try { //最近一次操作时间 fileTimestamp = resource.lastModified(); if (propHolder != null && propHolder.getFileTimestamp() == fileTimestamp) { if (logger.isDebugEnabled()) { logger.debug("Re-caching properties for filename [" + filename + "] - file hasn't been modified"); } //设置刷新时间 propHolder.setRefreshTimestamp(refreshTimestamp); return propHolder; } } catch (IOException ex) { // Probably a class path resource: cache it forever. if (logger.isDebugEnabled()) { logger.debug( resource + " could not be resolved in the file system - assuming that is hasn't changed", ex); } fileTimestamp = -1; } } try { //从给定的resource中加载properties Properties props = loadProperties(resource, filename); propHolder = new PropertiesHolder(props, fileTimestamp); } catch (IOException ex) { if (logger.isWarnEnabled()) { logger.warn("Could not parse properties file [" + resource.getFilename() + "]", ex); } // 加载报错,创建一个空的PropertiesHolder对象 propHolder = new PropertiesHolder(); } } else { // Resource 不存在 if (logger.isDebugEnabled()) { logger.debug("No properties file found for [" + filename + "] - neither plain properties nor XML"); } // 创建一个空的PropertiesHolder对象 propHolder = new PropertiesHolder(); } //设置刷新时间 propHolder.setRefreshTimestamp(refreshTimestamp); //放入缓存 this.cachedProperties.put(filename, propHolder); return propHolder; } /** * 解析给定的resource资源中返回对应properties对象 * @param resource the resource to load from * @param filename the original bundle filename (basename + Locale) * @return the populated Properties instance * @throws IOException if properties loading failed */ protected Properties loadProperties(Resource resource, String filename) throws IOException { InputStream is = resource.getInputStream(); Properties props = new Properties(); try { //xml资源文件 if (resource.getFilename().endsWith(XML_SUFFIX)) { if (logger.isDebugEnabled()) { logger.debug("Loading properties [" + resource.getFilename() + "]"); } //解析xml文件 this.propertiesPersister.loadFromXml(props, is); } else { //优先取filename所对应编码设置,没有则取默认设置 String encoding = null; if (this.fileEncodings != null) { encoding = this.fileEncodings.getProperty(filename); } if (encoding == null) { encoding = this.defaultEncoding; } if (encoding != null) { if (logger.isDebugEnabled()) { logger.debug("Loading properties [" + resource.getFilename() + "] with encoding '" + encoding + "'"); } this.propertiesPersister.load(props, new InputStreamReader(is, encoding)); } else { if (logger.isDebugEnabled()) { logger.debug("Loading properties [" + resource.getFilename() + "]"); } this.propertiesPersister.load(props, is); } } return props; } finally { is.close(); } } /** * 清除所有资源包对应的properties缓存。 */ public void clearCache() { logger.debug("Clearing entire resource bundle cache"); synchronized (this.cachedProperties) { this.cachedProperties.clear(); } synchronized (this.cachedMergedProperties) { this.cachedMergedProperties.clear(); } } /** * 清除此MessageSource及其所有父资源的缓存。 * @see #clearCache */ public void clearCacheIncludingAncestors() { clearCache(); if (getParentMessageSource() instanceof ReloadableResourceBundleMessageSource) { ((ReloadableResourceBundleMessageSource) getParentMessageSource()).clearCacheIncludingAncestors(); } } @Override public String toString() { return getClass().getName() + ": basenames=[" + StringUtils.arrayToCommaDelimitedString(this.basenames) + "]"; } /** * PropertiesHolder for caching. * 存储源文件的最后修改的时间戳以进行有效的更改检测,以及上次刷新尝试的时间戳(每次缓存条目重新验证时更新)。 */ protected class PropertiesHolder { //所持有properties对象 private Properties properties; //源文件的最后修改的时间戳 private long fileTimestamp = -1; //刷新时间 private long refreshTimestamp = -1; /** 缓存:每个code已经生成的各区域所对应MessageFormats */ private final Map> cachedMessageFormats = new HashMap>(); public PropertiesHolder(Properties properties, long fileTimestamp) { this.properties = properties; this.fileTimestamp = fileTimestamp; } public PropertiesHolder() { } public Properties getProperties() { return properties; } public long getFileTimestamp() { return fileTimestamp; } public void setRefreshTimestamp(long refreshTimestamp) { this.refreshTimestamp = refreshTimestamp; } public long getRefreshTimestamp() { return refreshTimestamp; } public String getProperty(String code) { if (this.properties == null) { return null; } return this.properties.getProperty(code); } public MessageFormat getMessageFormat(String code, Locale locale) { if (this.properties == null) { return null; } synchronized (this.cachedMessageFormats) { //先尝试走缓存 Map localeMap = this.cachedMessageFormats.get(code); if (localeMap != null) { MessageFormat result = localeMap.get(locale); if (result != null) { return result; } } //缓存没有,则properties中code所对应的值 String msg = this.properties.getProperty(code); if (msg != null) { if (localeMap == null) { localeMap = new HashMap(); this.cachedMessageFormats.put(code, localeMap); } //构建 MessageFormat 放入缓存 MessageFormat result = createMessageFormat(msg, locale); localeMap.put(locale, result); return result; } return null; } } } }

MessageFormat 消息格式化组件

这个类的功能比较强大,主要就是将消息串、参数数组格式化成字符串。例如:“{0}去上学”和参数“小明”,格式化成:“小明去上学”!

//’消息格式化组件‘
public class MessageFormat extends Format {

    private static final long serialVersionUID = 6479157306784022952L;


    private int maxOffset = -1;

    //构造方法
    public MessageFormat(String pattern) {
        this.locale = Locale.getDefault(Locale.Category.FORMAT);
        applyPattern(pattern);
    }

    //构造方法
    public MessageFormat(String pattern, Locale locale) {
        this.locale = locale;
        applyPattern(pattern);
    }

    //消息区域设置
    public void setLocale(Locale locale) {
        this.locale = locale;
    }

    //获取消息区域
    public Locale getLocale() {
        return locale;
    }

    //格式化
    public final String format (Object obj) {
        return format(obj, new StringBuffer(), new FieldPosition(0)).toString();
    }

    public final StringBuffer format(Object arguments, StringBuffer result,
                                     FieldPosition pos)
    {
        return subformat((Object[]) arguments, result, pos, null);
    }

    //======省略很多代码^_^========/

}

你可能感兴趣的:(jdk8源码阅读)