Spring国际化消息格式化原理

1,618 阅读17分钟

1.MessageSource-消息转化的顶层接口

      首先我们看下整体的类图:

      org.springframework.context.MessageSource是解析消息的接顶层接口,支持参数和国际化信息,它提供了两个开箱即用的实现:org.springframework.context.support.ResourceBundleMessageSource和org.springframework.context.support.RelaodableResourceBundleMessageSource,这两个类我们后面说。现在看下这个类的具体方法:

public interface MessageSource {
        /**
	 * 用于从MessageSource检索消息的基本方法。如果找不到指定语言环境的消息,则使用默认消息。
	 * 使用标准库提供的MessageFormat功能,传入的所有参数都将成为替换值
	 * @param code
	 * @param args
	 * @param defaultMessage
	 * @param locale
	 * @return
	 */
	String getMessage(String code, Object[] args, String defaultMessage, Locale locale);

	/**
	 * 与上一个方法基本相同,但有一个区别:无法指定默认消息。如果找不到消息,会抛出异常
	 * @param code
	 * @param args
	 * @param locale
	 * @return
	 * @throws NoSuchMessageException
	 */
	String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;

	/**
	 * 上述方法中使用的所有属性都封装在一个名为MessageSourceResolvable类中
	 * @param resolvable
	 * @param locale
	 * @return
	 * @throws NoSuchMessageException
	 */
	String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;

}

      可以看到这个接口提供了三个重载的方法,这三个方法的,目的都是为了获取消息。第一个方法可以传入默认消息,第二个方法在拿不到消息的时候会抛出异常,第三个方法相当于前两个方法的综合,把前两个方法的参数封装成了一个类,并且拿不到消息的时候会抛出异常,下面我们来看下这个类。

    1.MessageSourceResolvable

          org.springframework.context.MessageSourceResolvable这个类也比较简单,就是对参数进行了封装,适用于org.springframework.context.MessageSource类的消息解析。

public interface MessageSourceResolvable {
	/**
	 *返回需要解析的消息的key,数组的最后一位是默认key
	 * @return
	 */
	String[] getCodes();

	/**
	 * 返回要解析的消息的参数
	 * @return
	 */
	Object[] getArguments();

	/**
	 * 返回默认
	 * @return
	 */
	String getDefaultMessage();
}

          它有一个默认实现类org.springframework.context.support.DefaultMessageSourceResolvable,通过这个默认实现类,我们就可以封装我们的参数。

    2.HierarchicalMessageSource

          org.springframework.context.HierarchicalMessageSource这个类的作用是设置父MessageSource以及获取MessageSource,目的是当某个MessageSource无法解析消息的时候,会调用其父MessageSource进行消息解析,如果都解析不到,则返回null。

public interface HierarchicalMessageSource extends MessageSource {

	/**
	 * Set the parent that will be used to try to resolve messages
	 * that this object can't resolve.
	 * @param parent the parent MessageSource that will be used to
	 * resolve messages that this object can't resolve.
	 * May be {@code null}, in which case no further resolution is possible.
	 */
	/**
	 * 设置父MessageSource的原因是:
	 * 如果某个MessageSource无法解析消息,而且通用的配置也无法解析,那么就会递归的调用父MessageSource进行消息解析
	 * @param parent
	 */
	void setParentMessageSource(MessageSource parent);

	/**
	 * Return the parent of this MessageSource, or {@code null} if none.
	 */
	MessageSource getParentMessageSource();

}

    3.MessageSourceSupport

          org.springframework.context.support.MessageSourceSupport是一个抽象类,基于java.text.MessageFormat的标准实现,通过java.text.MessageFormat对消息进行格式化。\color{#00ffff}{MessageSource的所有实现类都是通过该类获取MessageFormat以及格式化的}。主要提供了一下两个功能:               1.创建对应Locale下的MessageFormat,并对消息进行格式化。               2.对已经创建的MessageFormat进行本地缓存,当下一次获取MessageFormat的时候会先从缓存中获取,缓存中没有,则创建并放入缓存。

/**
	 * Cache to hold already generated MessageFormats per message.
	 * Used for passed-in default messages. MessageFormats for resolved
	 * codes are cached on a specific basis in subclasses.
	 */
	/**
	 * 缓存已经创建的MessageFormat。
	 * 当调用formatMessage方法进行格式化的时候,会先从缓存中获取MessageFormat,如果不存在,才会调用createMessageFormat方法来创建合适的MessageFormat
	 */
	private final Map<String, Map<Locale, MessageFormat>> messageFormatsPerMessage =
			new HashMap<String, Map<Locale, MessageFormat>>();
/**
	 * Format the given message String, using cached MessageFormats.
	 * By default invoked for passed-in default messages, to resolve
	 * any argument placeholders found in them.
	 * @param msg the message to format
	 * @param args array of arguments that will be filled in for params within
	 * the message, or {@code null} if none
	 * @param locale the Locale used for formatting
	 * @return the formatted message (with resolved arguments)
	 */
	/**
	 * 通过java.text.MessageFormat类根据不同Locale格式化消息
	 * @param msg
	 * @param args
	 * @param locale
	 * @return
	 */
	protected String formatMessage(String msg, Object[] args, Locale locale) {
		if (msg == null || (!isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args))) {
			return msg;
		}
		MessageFormat messageFormat = null;
		synchronized (this.messageFormatsPerMessage) {
			//先从缓存中取
			Map<Locale, MessageFormat> messageFormatsPerLocale = this.messageFormatsPerMessage.get(msg);
			if (messageFormatsPerLocale != null) {
				messageFormat = messageFormatsPerLocale.get(locale);
			}
			else {
				messageFormatsPerLocale = new HashMap<Locale, MessageFormat>();
				this.messageFormatsPerMessage.put(msg, messageFormatsPerLocale);
			}
			if (messageFormat == null) {
				try {
					//创建java.text.MessageFormat并放入缓存
					messageFormat = createMessageFormat(msg, locale);
				}
				catch (IllegalArgumentException ex) {
					// Invalid message format - probably not intended for formatting,
					// rather using a message structure with no arguments involved...
					if (isAlwaysUseMessageFormat()) {
						throw ex;
					}
					// Silently proceed with raw message if format not enforced...
					messageFormat = INVALID_MESSAGE_FORMAT;
				}
				messageFormatsPerLocale.put(locale, messageFormat);
			}
		}
		if (messageFormat == INVALID_MESSAGE_FORMAT) {
			return msg;
		}
		synchronized (messageFormat) {
			return messageFormat.format(resolveArguments(args, locale));
		}
	}

	/**
	 * Create a MessageFormat for the given message and Locale.
	 * @param msg the message to create a MessageFormat for
	 * @param locale the Locale to create a MessageFormat for
	 * @return the MessageFormat instance
	 */
	/**
	 * 创建对应Locale的MessageFormat
	 * @param msg
	 * @param locale
	 * @return
	 */
	protected MessageFormat createMessageFormat(String msg, Locale locale) {
		return new MessageFormat((msg != null ? msg : ""), locale);
	}

    4.DelagtingMessageSource

          org.springframework.context.support.DelagtingMessageSource是当Spring容器找不到名称messageSource的bean的时候,默认创建的MessageSource,可以在org.springframework.context.support.AbstractApplicationContext#initMessageSource方法中看到。该类本质上是不会消息进行处理的,只会交给它的父MessageSource处理,当然如果是具有defaultMessage的情况下,会调用org.springframework.context.support.MessageSupport#renderDefaultMessage方法来处理。

        @Override
	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);
		}
	}

	@Override
	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);
		}
	}

	@Override
	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);
		}
	}

    5.AbstractMessageSource

          org.springframework.context.support.AbstractMessageSource是一个模版类,实现了消息的通用处理,从而可以轻松的针对具体的MessageSource调用特定的策略。           首先看下该类对MessageSource的实现:

@Override
	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;
			}
		}
        //调用的MessageSourceSupport的方法
		return renderDefaultMessage(defaultMessage, args, locale);
	}

	@Override
	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);
	}

	@Override
	public final String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
		String[] codes = resolvable.getCodes();
		if (codes != null) {
			for (String code : codes) {
				String message = getMessageInternal(code, resolvable.getArguments(), locale);
				if (message != null) {
					return message;
				}
			}
		}
		String defaultMessage = getDefaultMessage(resolvable, locale);
		if (defaultMessage != null) {
			return defaultMessage;
		}
		throw new NoSuchMessageException(!ObjectUtils.isEmpty(codes) ? codes[codes.length - 1] : null, locale);
	}

          从这段源码的实现,我们不难看出这里面有两个核心的方法:               1.org.springframewrok.context.support.AbstractMessageSource#getMessageInternal                   在给定的语言环境中将给定的代码和参数解析消息。               2.org.springframewrok.context.support.AbstractMessageSource#getDefaultMessage                   在不存在defaultMessage的时候,直接返回code作为defaultMessage,存在的时候,调用MessageSourceSupport进行格式化并返回。

          getDefaultMessage

protected String getDefaultMessage(MessageSourceResolvable resolvable, Locale locale) {
		String defaultMessage = resolvable.getDefaultMessage();
		String[] codes = resolvable.getCodes();
		if (defaultMessage != null) {
			if (!ObjectUtils.isEmpty(codes) && defaultMessage.equals(codes[0])) {
				// Never format a code-as-default-message, even with alwaysUseMessageFormat=true
				//将key作为默认消息,不用去格式化
				return defaultMessage;
			}
			//调用org.springframework.context.support.MessageSourceSupport#renderDefaultMessage根据语言环境进行格式化
			return renderDefaultMessage(defaultMessage, resolvable.getArguments(), locale);
		}
		return (!ObjectUtils.isEmpty(codes) ? getDefaultMessage(codes[0]) : null);
	}
protected String getDefaultMessage(String code) {
		if (isUseCodeAsDefaultMessage()) {
			return code;
		}
		return null;
	}

          getMessageInternal

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)) {
			// 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.
			//没有参数
			//按道理说是不需要java.text.MessageFormat类的,但是默认实现还是调用了
			//我们在子类中可以重写org.springframework.context.support.AbstractMessageSource#resolveCodeWithoutArguments
			//是一个可以优化的点
			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.
			//有参数
			//将参数转为参数数组,主要是对org.springframework.context.MessageSourceResolvable类进行参数解析
			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.
		//如果上面没有找到对应Locale的Message,那么就从与Locale无关的公共消息中获取消息
		//private Properties commonMessages;
		//当前commonMessages就是Properties
		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.
		//如果上面都没有找到,则通过org.springframework.context.HierarchicalMessageSource#getPraentMessageSource从父类中去找
		return getMessageFromParent(code, argsToUse, locale);
}


@Override
protected Object[] resolveArguments(Object[] args, Locale locale) {
		if (args == null) {
			return new Object[0];
		}
		List<Object> resolvedArgs = new ArrayList<Object>(args.length);
		for (Object arg : args) {
			if (arg instanceof MessageSourceResolvable) {
				resolvedArgs.add(getMessage((MessageSourceResolvable) arg, locale));
			}
			else {
				resolvedArgs.add(arg);
			}
		}
		return resolvedArgs.toArray();
}

/**
	 * 解析没有参数的消息,子类可以重写,优化java.text.MessageFormat的问题
	 * @param code
	 * @param locale
	 * @return
	 */
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;
}

/**
	 * 解析有参数的消息,必须完全由子类实现
	 * @param code
	 * @param locale
	 * @return
	 */
protected abstract MessageFormat resolveCode(String code, Locale locale);

          从上述代码中,我们可以看出来模版类主要做了以下几件事情和两个钩子方法交给子类实现:               提供了模板方法解析消息                   1.对于没有args并且参数为空的org.springframework.context.support.AbstractMessageSource#resolveCodeWithoutArguments方法可以使用当前类的,也可以交给子类优化。                   2.对于有参数的先调用org.springframework.context.support.AbstractMessageSource#resolveArguments把参数转为参数数组,然后必须调用子类实现的org.springframework.context.support.AbstractMessageSource#resolveCode获取java.text.MessageFormat。                   3.如果都没有获取到Message或MessageFormat,则从通用的Properties中获取。                   4.如果上述三步都没有获取到,则利用org.springframework.context.HierarchicalMessageSource递归通过父MessageSource来解析消息。

    6.ResouceBundleMessageSource

          org.springframework.context.support.ResouceBundleMessageSource是基于java.util.ResouceBundle实现的。查看org.springframework.context.support.ResouceBundleMessageSource.MessageSourceControl可以看到,其实现了java.util.ResouceBunle.Control来解析国际化,以及增加了编解码的功能,为了解决国际化乱码的问题。\color{#00ffff}{这个类的缺点很明显,只能从类路径读取资源}

          现在我们来看下该类的resolveCodeWiththoutArguments方法和resolveCode方法

 /**
     * 没有参数
     * 优化了父类无参数还获取java.text.MessageFormat的问题
     */
    @Override
    protected String resolveCodeWithoutArguments(String code, Locale locale) {
        Set<String> basenames = getBasenameSet();
        for (String basename : basenames) {
            ResourceBundle bundle = getResourceBundle(basename, locale);
            if (bundle != null) {
                String result = getStringOrNull(bundle, code);
                if (result != null) {
                    return result;
                }
            }
        }
        return null;
    }

    /**
     * 当getMessage有参数的时候,父类会调用该方法
     */
    @Override
    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;
    }

/**
     * 根据语言环境找到对应的ResourceBundle
     * @param basename
     * @param locale
     * @return
     */
    protected ResourceBundle getResourceBundle(String basename, Locale locale) {
        //如果指定了本地缓存的时间,则会超时后重新ResourceBundle,并丢进缓存中
        if (getCacheMillis() >= 0) {
            // Fresh ResourceBundle.getBundle call in order to let ResourceBundle
            // do its native caching, at the expense of more extensive lookup steps.
            //调用ResourceBundle.getBundle(),并缓存到本地,但是消耗的时间过长
            return doGetBundle(basename, locale);
        }
        //没有指定本地缓存时间,则直接存储在了cachedResourceBundles中,永远缓存
        else {
            // Cache forever: prefer locale cache over repeated getBundle calls.
            synchronized (this.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;
                }
            }
        }
    }

  /**
     * 调用原生的java.util.ResourceBundle方法新建ResouceBundle
     * @param basename
     * @param locale
     * @return
     * @throws MissingResourceException
     */
    protected ResourceBundle doGetBundle(String basename, Locale locale) throws MissingResourceException {
        return ResourceBundle.getBundle(basename, locale, getBundleClassLoader(), new MessageSourceControl());
    }

          查看上述代码org.springframework.context.support.ResouceBundleMessageSource#resolveCodeWithoutArguments拿到了ResourceBundle,然后通过java.util.ResourceBundle#getString获取对应的Message并返回。           org.springframework.context.support.ResouceBundleMessageSource#resolveCode拿到了ResouceBundle,然后根据ResouceBundle获取对应的java.text.MessageFormat,最后格式化消息并返回。           org.springframework.context.support.ResourceBundleMessageSource#getResourceBundle主要做了两件事:               1.如果指定了本地缓存时间,则缓存超时后,重新获取ResourceBundle,并丢进缓存中。               2.没有指定缓存时间,直接从org.springframework.context.support.ResourceBundleMessageSource#cachedResourceBundles缓存中获取。

    7.ReloadableResourceBundleMessageSource

          该类相对于基于JDK标准实现的ResouceBundleMessageSource实现更加灵活。特别是,它允许从任何Spring资源位置读取文件(不仅仅是类路径),并支持热重载bundle属性文件(同时在两者之间有效的缓存它们)。           下面我们看下重写的方法:

@Override
    protected String resolveCodeWithoutArguments(String code, Locale locale) {
        //从缓存中获取
        if (getCacheMillis() < 0) {
            PropertiesHolder propHolder = getMergedProperties(locale);
            String result = propHolder.getProperty(code);
            if (result != null) {
                return result;
            }
        } else {
            //加载资源
            for (String basename : getBasenameSet()) {
                List<String> filenames = calculateAllFilenames(basename, locale);
                for (String filename : filenames) {
                    PropertiesHolder propHolder = getProperties(filename);
                    String result = propHolder.getProperty(code);
                    if (result != null) {
                        return result;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Resolves the given message code as key in the retrieved bundle files,
     * using a cached MessageFormat instance per message code.
     */
    @Override
    protected MessageFormat resolveCode(String code, Locale locale) {
        //从缓存中获取
        if (getCacheMillis() < 0) {
            PropertiesHolder propHolder = getMergedProperties(locale);
            MessageFormat result = propHolder.getMessageFormat(code, locale);
            if (result != null) {
                return result;
            }
        } else {//加载资源,获取
            for (String basename : getBasenameSet()) {
                List<String> 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;
    }

          通过上述源码,我们看到该类进行消息解析的时候主要分为两部分:一是判断是不是有缓存,直接从缓存中取,二是直接解析资源,从新解析出来的资源获取消息。下面我们看下核心的流程:               1.PropertiesHolder                   这个类是一个内部类,负责缓存已经加载了的Properties资源和MessageFormat,而且会根据时间戳来判断是否要刷新资源,也就是热加载。这里我们先知道这个类,后面的核心流程基本上都是围绕这个类以及三个缓存。

    //缓存文件名称
    private final ConcurrentMap<String, Map<Locale, List<String>>> cachedFilenames =
            new ConcurrentHashMap<String, Map<Locale, List<String>>>();

    //根据文件名称缓存PropertiesHolder
    private final ConcurrentMap<String, PropertiesHolder> cachedProperties =
            new ConcurrentHashMap<String, PropertiesHolder>();

    //根据Locale缓存PropertiesHolder
    private final ConcurrentMap<Locale, PropertiesHolder> cachedMergedProperties =
            new ConcurrentHashMap<Locale, PropertiesHolder>();
 /**
     * 该类用于缓存,包括了两部分内容的缓存
     * 1.Properties资源文件,适用于没有参数的时候,直接从Properties获取消息
     * 2.java.text.MessageFormat,根据code和Locale缓存了MessageFormat,适用于有参数的时候进行参数格式化
     */
    protected class PropertiesHolder {

        /**
         * k-v
         */
        private final Properties properties;

        /**
         * 最后修改的时间戳
         */
        private final long fileTimestamp;

        /**
         * 最后一次刷新的时间戳
         */
        private volatile long refreshTimestamp = -2;

        private final ReentrantLock refreshLock = new ReentrantLock();

        /**
         * 缓存已经生成的MessageFormat
         */
        private final ConcurrentMap<String, Map<Locale, MessageFormat>> cachedMessageFormats =
                new ConcurrentHashMap<String, Map<Locale, MessageFormat>>();

        public PropertiesHolder() {
            this.properties = null;
            this.fileTimestamp = -1;
        }

        public PropertiesHolder(Properties properties, long fileTimestamp) {
            this.properties = properties;
            this.fileTimestamp = fileTimestamp;
        }

        public Properties getProperties() {
            return this.properties;
        }

        public long getFileTimestamp() {
            return this.fileTimestamp;
        }

        public void setRefreshTimestamp(long refreshTimestamp) {
            this.refreshTimestamp = refreshTimestamp;
        }

        public long getRefreshTimestamp() {
            return this.refreshTimestamp;
        }

        /**
         * 直接获取消息,针对于没有参数的
         *
         * @param code
         * @return
         */
        public String getProperty(String code) {
            if (this.properties == null) {
                return null;
            }
            return this.properties.getProperty(code);
        }

      /**
        * 有参数的,获取MessageFormat
        **/
        public MessageFormat getMessageFormat(String code, Locale locale) {
            if (this.properties == null) {
                return null;
            }
            //从缓存中获取已经生成的MessageFormat
            Map<Locale, MessageFormat> localeMap = this.cachedMessageFormats.get(code);
            if (localeMap != null) {
                MessageFormat result = localeMap.get(locale);
                if (result != null) {
                    return result;
                }
            }
            //缓存中没有就直接获取消息,生成MessageFormat
            String msg = this.properties.getProperty(code);
            if (msg != null) {
                if (localeMap == null) {
                    localeMap = new ConcurrentHashMap<Locale, MessageFormat>();
                    Map<Locale, MessageFormat> existing = this.cachedMessageFormats.putIfAbsent(code, localeMap);
                    if (existing != null) {
                        localeMap = existing;
                    }
                }
                //调用org.springframework.context.support.MessageSourceSupport#createMessageFormat
                MessageFormat result = createMessageFormat(msg, locale);
                localeMap.put(locale, result);
                return result;
            }
            return null;
        }
    }

              2.getMergedProperties                   从这个方法开始,我们一步步看,就可以整个的核心流程。这一系列方法的调用还涉及到三个缓存:

/**
     * 三步获取:
     * 1.通过Locale判断cachedMergedProperties缓存中有没有PropertiesHolder
     * 2.根据basenames和Locale加载filenames,判断cachedFilenames缓存中有没有filename
     * 3.根据filenames判断cachedProperties缓存有没有PropertiesHolder
     * 通过2,3两步解析资源获取了PropertiesHolder会缓存到cachedMergedProperties缓存中有没有PropertiesHolder中
     *
     * @param locale
     * @return
     */
    protected PropertiesHolder getMergedProperties(Locale locale) {
        //从缓存中获取
        PropertiesHolder mergedHolder = this.cachedMergedProperties.get(locale);
        if (mergedHolder != null) {
            return mergedHolder;
        }

        //生成新的PropertiesHolder并放入缓存
        Properties mergedProps = newProperties();
        long latestTimestamp = -1;
        String[] basenames = StringUtils.toStringArray(getBasenameSet());
        for (int i = basenames.length - 1; i >= 0; i--) {
            //filename缓存
            List<String> filenames = calculateAllFilenames(basenames[i], locale);
            for (int j = filenames.size() - 1; j >= 0; j--) {
                String filename = filenames.get(j);
                //PropertiesHolder缓存
                PropertiesHolder propHolder = getProperties(filename);
                if (propHolder.getProperties() != null) {
                    mergedProps.putAll(propHolder.getProperties());
                    if (propHolder.getFileTimestamp() > latestTimestamp) {
                        latestTimestamp = propHolder.getFileTimestamp();
                    }
                }
            }
        }

        mergedHolder = new PropertiesHolder(mergedProps, latestTimestamp);
        PropertiesHolder existing = this.cachedMergedProperties.putIfAbsent(locale, mergedHolder);
        if (existing != null) {
            mergedHolder = existing;
        }
        return mergedHolder;
    }

              3.calculateAllFilenames

/**
* 获取资源文件名称 拼接之后的资源文件名称交给Properties找到对应的资源进行加载
**/
protected List<String> calculateAllFilenames(String basename, Locale locale) {
        Map<Locale, List<String>> localeMap = this.cachedFilenames.get(basename);
        if (localeMap != null) {
            List<String> filenames = localeMap.get(locale);
            if (filenames != null) {
                return filenames;
            }
        }

        // Filenames for given Locale
        List<String> filenames = new ArrayList<String>(7);
        filenames.addAll(calculateFilenamesForLocale(basename, locale));

        // Filenames for default Locale, if any
        if (isFallbackToSystemLocale()) {
            Locale defaultLocale = Locale.getDefault();
            if (!locale.equals(defaultLocale)) {
                List<String> fallbackFilenames = calculateFilenamesForLocale(basename, defaultLocale);
                for (String fallbackFilename : fallbackFilenames) {
                    if (!filenames.contains(fallbackFilename)) {
                        // Entry for fallback locale that isn't already in filenames list.
                        filenames.add(fallbackFilename);
                    }
                }
            }
        }

        // Filename for default bundle file
        filenames.add(basename);

        if (localeMap == null) {
            localeMap = new ConcurrentHashMap<Locale, List<String>>();
            Map<Locale, List<String>> existing = this.cachedFilenames.putIfAbsent(basename, localeMap);
            if (existing != null) {
                localeMap = existing;
            }
        }
        localeMap.put(locale, filenames);
        return filenames;
    }

              4.calculateFilenamesForLocale

/**
* 根据Locale拼接资源的名称
**/
protected List<String> calculateFilenamesForLocale(String basename, Locale locale) {
        List<String> result = new ArrayList<String>(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;
    }

              5.getProperties

/**
* 获取PropertiesHolder
**/
pprotected PropertiesHolder getProperties(String filename) {
        PropertiesHolder propHolder = this.cachedProperties.get(filename);
        long originalTimestamp = -2;

        if (propHolder != null) {
            originalTimestamp = propHolder.getRefreshTimestamp();
            if (originalTimestamp == -1 || originalTimestamp > System.currentTimeMillis() - getCacheMillis()) {
                // Up to date
                return propHolder;
            }
        } else {
            propHolder = new PropertiesHolder();
            PropertiesHolder existingHolder = this.cachedProperties.putIfAbsent(filename, propHolder);
            if (existingHolder != null) {
                propHolder = existingHolder;
            }
        }

        // At this point, we need to refresh...
        if (this.concurrentRefresh && propHolder.getRefreshTimestamp() >= 0) {
            // A populated but stale holder -> could keep using it.
            if (!propHolder.refreshLock.tryLock()) {
                // Getting refreshed by another thread already ->
                // let's return the existing properties for the time being.
                return propHolder;
            }
        } else {
            propHolder.refreshLock.lock();
        }
        try {
            PropertiesHolder existingHolder = this.cachedProperties.get(filename);
            if (existingHolder != null && existingHolder.getRefreshTimestamp() > originalTimestamp) {
                return existingHolder;
            }
            return refreshProperties(filename, propHolder);
        } finally {
            propHolder.refreshLock.unlock();
        }
    }

              6.refreshProperties

/**
*加载资源已经判断是否要刷新(热加载)
**/
protected PropertiesHolder refreshProperties(String filename, PropertiesHolder propHolder) {
        long refreshTimestamp = (getCacheMillis() < 0 ? -1 : System.currentTimeMillis());

        Resource resource = this.resourceLoader.getResource(filename + PROPERTIES_SUFFIX);
        if (!resource.exists()) {
            resource = this.resourceLoader.getResource(filename + XML_SUFFIX);
        }

        if (resource.exists()) {
            long fileTimestamp = -1;
            if (getCacheMillis() >= 0) {
                // Last-modified timestamp of file will just be read if caching with timeout.
                //如果缓存超时了,只能读
                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 it hasn't changed", ex);
                    }
                    fileTimestamp = -1;
                }
            }
            try {
                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);
                }
                // Empty holder representing "not valid".
                propHolder = new PropertiesHolder();
            }
        } else {
            // Resource does not exist.
            if (logger.isDebugEnabled()) {
                logger.debug("No properties file found for [" + filename + "] - neither plain properties nor XML");
            }
            // Empty holder representing "not found".
            propHolder = new PropertiesHolder();
        }

        propHolder.setRefreshTimestamp(refreshTimestamp);
        this.cachedProperties.put(filename, propHolder);
        return propHolder;
}

              7.loadProperties

 /**
     * 真正的去加载资源
     * @param resource
     * @param filename
     * @return
     * @throws IOException
     */
protected Properties loadProperties(Resource resource, String filename) throws IOException {
        InputStream is = resource.getInputStream();
        Properties props = newProperties();
        try {
            if (resource.getFilename().endsWith(XML_SUFFIX)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Loading properties [" + resource.getFilename() + "]");
                }
                this.propertiesPersister.loadFromXml(props, is);
            } else {
                String encoding = null;
                if (this.fileEncodings != null) {
                    encoding = this.fileEncodings.getProperty(filename);
                }
                if (encoding == null) {
                    encoding = getDefaultEncoding();
                }
                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();
        }
    }

              按照上述顺序看完,你就应该知道了ReloadResourceBundleMessageSource的消息解析的流程。               总结一下:               org.springframework.context.support.ReloadableResourceBundleMessageSource#loadProperties               这⾥通过org.springframework.context.support.ReloadableResourceBundleMessageSource#calculateAllFilenames 以及               org.springframework.context.support.ReloadableResourceBundleMessageSource#calculateFilenamesForLocale 计算出来的对应⽅⾔的路径加载到properties               中,然后把获取到的properties 放 到org.springframework.context.support.ReloadableResourceBundleMessageSource.PropertiesHolder 中持有,当前类会存储               源⽂件的最后修改的时间戳,然后判断最后修改的时间戳和               当前时间差值⽐较,判断是否超过了允许的最⼤缓存时间。

    8.StaticMessageSource

          StaticMessageSource很少使用,相比之下就很简单了,只是简单的从map中获取。

        /** Map from 'code + locale' keys to message Strings */
	private final Map<String, String> messages = new HashMap<String, String>();

	private final Map<String, MessageFormat> cachedMessageFormats = new HashMap<String, MessageFormat>();

    @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();
		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;
		}
	}