Spring之国际化信息MessageSource源码阅读
来源:互联网 发布:淘宝附近的人取消了吗 编辑:程序博客网 时间:2024/06/06 21:05
MessageSource架构图
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}参数中包含的所有属性来解析消息。 * <p>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<String, Map<Locale, MessageFormat>> messageFormatsPerMessage = new HashMap<String, Map<Locale, MessageFormat>>(); /** * 设置是否始终应用’消息格式组件‘,解析没有参数的消息。 * <p>例如:,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<Locale, MessageFormat> messageFormatsPerLocale = this.messageFormatsPerMessage.get(msg); if (messageFormatsPerLocale != null) { messageFormat = messageFormatsPerLocale.get(locale); } else {//缓存为空,设置初始化空map值 messageFormatsPerLocale = new HashMap<Locale, MessageFormat>(); 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; //与区域设置无关的公共消息,消息代码为关键字 // <bean id="commonMessages" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> // <property name="locations"> // <list> // <value>classpath*:application.properties</value> // </list> // </property> //</bean> 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. * <p>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<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(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<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(); //如果为手动关联的消息串,直接返回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<String, String> messages, Locale locale) { Assert.notNull(messages, "Messages Map must not be null"); for (Map.Entry<String, String> entry : messages.entrySet()) { addMessage(entry.getKey(), locale, entry.getValue()); } } @Override public String toString() { return getClass().getName() + ": " + this.messages; }}
ResourceBundleMessageSource
//实现类二:该实现类允许用户通过beanName指定一个资源名(包括类路径的全限定资源名),或通过beanNames指定一组资源名。//使用方式://<bean id="myResource" lass="org.springframework.context.support. ReloadableResourceBundleMessageSource"> // <property name="basenames"> // <list> // <value>com/baobaotao/i18n/fmt_resource</value> // </list> // </property> //</bean> 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<String, Map<Locale, ResourceBundle>> cachedResourceBundles = new HashMap<String, Map<Locale, ResourceBundle>>(); //缓存ResourceBundle和code及local所对应的MessageFormat private final Map<ResourceBundle, Map<String, Map<Locale, MessageFormat>>> cachedBundleMessageFormats = new HashMap<ResourceBundle, Map<String, Map<Locale, MessageFormat>>>(); //设置资源文件 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<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; } } } } //获取给定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<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; } } } //缓存为空的话,执行下面步骤 //获取资源包中指定key所对应的值 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; } } //获取资源包中指定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<InputStream>() { 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指定一组资源名。//使用方式://<bean id="myResource" lass="org.springframework.context.support. ReloadableResourceBundleMessageSource"> // <property name="basenames"> // <list> // <value>com/baobaotao/i18n/fmt_resource</value> // </list> // </property> // <!--① 刷新资源文件的周期,以秒为单位--> // <property name="cacheSeconds" value="5"/> //</bean> 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<String, Map<Locale, List<String>>> cachedFilenames = new HashMap<String, Map<Locale, List<String>>>(); /** 缓存来保存每个文件名已经加载的属性 */ private final Map<String, PropertiesHolder> cachedProperties = new HashMap<String, PropertiesHolder>(); /** 缓存以保存每个区域的合并加载属性*/ private final Map<Locale, PropertiesHolder> cachedMergedProperties = new HashMap<Locale, PropertiesHolder>(); 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<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; } /** * 将给定的消息代码解析为检索到的包文件中的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<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; } /** * 获取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<String> calculateAllFilenames(String basename, Locale locale) { synchronized (this.cachedFilenames) { Map<Locale, List<String>> localeMap = this.cachedFilenames.get(basename); if (localeMap != null) { List<String> filenames = localeMap.get(locale); if (filenames != null) { return filenames; } } List<String> filenames = new ArrayList<String>(7); filenames.addAll(calculateFilenamesForLocale(basename, locale)); if (this.fallbackToSystemLocale && !locale.equals(Locale.getDefault())) { List<String> 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<Locale, List<String>>(); 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". * <p>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<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; } //从缓存或新加载中获取给定文件名的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<String, Map<Locale, MessageFormat>> cachedMessageFormats = new HashMap<String, Map<Locale, MessageFormat>>(); 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<Locale, MessageFormat> 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<Locale, MessageFormat>(); 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); } //======省略很多代码^_^========/}
- Spring之国际化信息MessageSource源码阅读
- Spring-国际化信息02-MessageSource接口
- Spring 利用MessageSource实现国际化
- Spring 利用MessageSource实现国际化
- 【Spring】Spring 利用MessageSource实现国际化
- Spring国际化--从数据库读取messageSource
- spring中MessageSource实现国际化i18n
- Spring(21)——国际化MessageSource
- spring的学习历程之利用MessageSource实现国际化占位符直接输出原因(一)
- Spring 国际化 bean的id必须是messageSource
- spring MessageSource
- Spring 源码阅读之BeanFactory
- spring源码阅读之BeanFactory
- Spring源码阅读之DefaultListableBeanFactory
- Spring源码阅读之-utils
- Spring 源码之 BeanDefinition阅读
- spring源码学习笔记-初始化(五)-MessageSource/事件监听器
- spring源码学习笔记-初始化(五)-MessageSource/事件监听器
- 文章标题
- html5新增语义化标签
- Cookie/Session机制详解
- 数据结构的线性表部分知识
- Android判断网络是否可用并且开启网络
- Spring之国际化信息MessageSource源码阅读
- POJ 3116 MegaCheckers 笔记
- 逆置反转单链表
- 记录一下我悲催的OI生涯
- activeMQ消息队列之JMS基础(附了一些代码帮助理解前期可跳过)
- HDU 5812 Distance 暴力+素数打表
- 机房重构之学生查看上机记录
- R中的Box-Cox变换
- 实验 6-1 1.针对符号连接文件和硬连接文件。测试以下函数,分析其执行过程并给出结论。 link() unlink() symlink() readlink() stat() lstat(