【Spring(十一)】国际化

32 篇文章 15 订阅

Spring的国际化是通过接口MessageSource来定义的。
先看下该接口:

public interface MessageSource {

	@Nullable
	String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);

	String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;

	String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;

}

一般用前两个就行,指定message的key和locale,如果有占位符替换,就传入args数组。
Spring的高级容器Application是继承了该接口的,说明Spring的高级容器也具备了国际化消息的能力。
但是最终的实现类一般是ReloadableResourceBundleMessageSource和ResourceBundleMessageSource,前者具备了定时刷新的能力。这里只看ResourceBundleMessageSource。
另外,其实java本省就提供了国际化的能力,主要是通过ResourceBundle实现的。而Spring的国际化底层还是通过ResourceBundle来实现的,可以理解为是对ResourceBundle的更高一级的封装。增强了消息的缓存以及增加了占位符替换的能力。

下面看下如何使用。
首先需要在spring的配置文件中配置消息的bean,这里用的是ResourceBundleMessageSource:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="messageSource"
          class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>message</value>
            </list>
        </property>
    </bean>
</beans>

“basenames”就是国际化资源文件的前缀,这里支持配置多个资源化文件。

下面配置国际化资源文件:
这里只配置一条,你好,xxx的文案,支持中文和美式英文的。

message_zh_CN.properties:
helloKey=\u4f60\u597d\uff0c{0}
中文文件需要使用native2ascii命令转换格式才行,命令:native2ascii raw.properties message_zh_CN.properties
message_en_US.properties:
helloKey=hello,{0}

再写一个测试类:

public class Main {
    public static void main(String args[]) {
        ApplicationContext context = new ClassPathXmlApplicationContext("message.xml");
        Locale locale = Locale.getDefault();
        System.out.println(context.getMessage("helloKey", new String[]{"ly"}, locale));
        System.out.println(context.getMessage("helloKey", new String[]{"ly"}, new Locale("en", "US")));
    }
}

打印结果如下:

你好,ly
hello,ly

Process finished with exit code 0

最后看下实现。

Spring高级容器对getMessage的实现,其实是在AbstractApplicationContext里的:

	@Override
	public String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
		return getMessageSource().getMessage(resolvable, locale);
	}
	private MessageSource getMessageSource() throws IllegalStateException {
		if (this.messageSource == null) {
			throw new IllegalStateException("MessageSource not initialized - " +
					"call 'refresh' before accessing messages via the context: " + this);
		}
		return this.messageSource;
	}
	/** MessageSource we delegate our implementation of this interface to. */
	@Nullable
	private MessageSource messageSource;

本身是代理给了内部的一个MessageSource类型的实例做的。
插一嘴啊,现在发现,Spring有好多类似这样的代理逻辑,类的签名上implement了一个接口,但是真正实现的时候确实代理给其他类做的。感觉也是一个不错的设计。一个模块可能有N多功能,根据接口分离原则,不同类型的功能需要分成到不同的接口。最终的实现类不一定实现所有的接口,可以用代理的方式,转给其他实现类,这样,该类在使用上,具备了所有接口所约束的能力,但是类本身却不会有太过臃肿,一些接口的实现是代理给其他类做的,两全其美。
好,言归正传。
那么,这个messageSource实例是什么时候赋值的呢?
refresh函数有一步:

	// Initialize message source for this context.
	nitMessageSource();

就是在这里赋值的:

	protected void initMessageSource() {
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
			this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
			// Make MessageSource aware of parent MessageSource.
			if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
				HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
				if (hms.getParentMessageSource() == null) {
					// Only set parent context as parent MessageSource if no parent MessageSource
					// registered already.
					hms.setParentMessageSource(getInternalParentMessageSource());
				}
			}
			if (logger.isTraceEnabled()) {
				logger.trace("Using MessageSource [" + this.messageSource + "]");
			}
		}
		else {
			// Use empty MessageSource to be able to accept getMessage calls.
			DelegatingMessageSource dms = new DelegatingMessageSource();
			dms.setParentMessageSource(getInternalParentMessageSource());
			this.messageSource = dms;
			beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
			if (logger.isTraceEnabled()) {
				logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
			}
		}
	}

从容器中取出name为“messageSource”的bean,所以是通过名字来取的,xml里的名字不能变。
最终实现是在ResourceBundleMessageSource的父类AbstractMessageSource中实现的:

	@Override
	public final String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale) {
		String msg = getMessageInternal(code, args, locale);
		if (msg != null) {
			return msg;
		}
		if (defaultMessage == null) {
			return getDefaultMessage(code);
		}
		return renderDefaultMessage(defaultMessage, args, locale);
	}

	@Nullable
	protected String getMessageInternal(@Nullable String code, @Nullable Object[] args, @Nullable 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.
			String message = resolveCodeWithoutArguments(code, locale);
			if (message != null) {
				return message;
			}
		}

		else {
			// Resolve arguments eagerly, for the case where the message
			// is defined in a parent MessageSource but resolvable arguments
			// are defined in the child MessageSource.
			argsToUse = resolveArguments(args, locale);

			MessageFormat messageFormat = resolveCode(code, locale);
			if (messageFormat != null) {
				synchronized (messageFormat) {
					return messageFormat.format(argsToUse);
				}
			}
		}

		// Check locale-independent common messages for the given message code.
		Properties commonMessages = getCommonMessages();
		if (commonMessages != null) {
			String commonMessage = commonMessages.getProperty(code);
			if (commonMessage != null) {
				return formatMessage(commonMessage, args, locale);
			}
		}

		// Not found -> check parent, if any.
		return getMessageFromParent(code, argsToUse, locale);
	}

里面区分了是否需要替换占位符,如果不需要就是走的resolveCodeWithoutArguments这个方法,这里就看下这个方法,因为整体上区别不大:

	@Nullable
	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;
	}

这里的resolveCode方法是在子类ResourceBundleMessageSource中实现的:

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

这里就是从之前的xml中取出basenames属性,其实就是资源文件的前缀。
然后遍历资源文件,每一个前缀都能取出一个ResourceBundle实例,该实例是有缓存的。然后再取出一个MessageFormat来格式化消息,该实例也是有缓存的。具体的取逻辑就不深入了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值