Java —— MessageFormat类 处理国际化

版权声明:转载请注明出处! https://blog.csdn.net/qq_19865749/article/details/70195063

一、MessageFormat 概览

java.text包中的 Fomart 接口是所有处理格式的基础接口,有三个子类:DateFormat、MessageFormat、NumberFormat。

MessageFormat 是专门处理文本格式的类,且没有子类。



二、MessageFormat 细节

1、构造函数:

MessageFormat(String pattern);			//pattern为字符串模式;使用默认的Locale.Category常量对应的语言格式
MessageFormat(String pattern,Locale locale);	//使用指定的locale对应的语言

2、静态方法:

MessageFormat.format(String pattern,Ojbect... arguments); //创建一次性使用的格式字符串


3、实例方法:

主要涉及三个功能:设置或返回pattern、设置或返回locale、设置或返回Format实例。

//设置与返回当前的pattern
public void applyPattern(String pattern);
public String toPattern();
//设置与返回当前的locale
public void setLocale(Locale locale);
public Locale getLocale();
//一次设置单个格式
public void setFormat(int formatElementIndex, Format newFormat);
public void setFormatByArgumentIndex(int argumentIndex, Format newFormat);
//一次设置多个格式
public void setFormats(Format[] newFormats);
public void setFormatsByArgumentIndex(Format[] newFormats);
//获取设置的格式
public Format[] getFormats();
public Format[] getFormatsByArgumentIndex();//不推荐。如果一个ArgumantsInex没有用于任何格式元素,则返回null。
//format。后两个参数是可选的
public String format(Object[] arguments[,StringBuffer result,FieldPosition position]);


三、基础实践

1、使用构造方法指定pattern与locale

public MessageFormat(String pattern,Locale local);


模式参数 pattern 的语法:

语法即:真个表达式为一个字符串,需要替换的参数信息放在花括号{}中。

"{参数索引} 字符串  \"{参数索引,格式类型,格式风格} 字符串\""


FormatType 与 FormatStyle 说明:



指定date与time要求传入的arguments 中对应对象为 Date类型,number 为数字类型。

Locale 类:

管理语言,用其常量可实现国际化。


实践:

Calendar calendar= Calendar.getInstance();
calendar.setTime(new Date());//calendar 只是管理时间,所以需要传入时间
String date=String.valueOf(calendar.get(Calendar.YEAR))+"."
	+String.valueOf(calendar.get(Calendar.MONTH)+1)+"."
	+String.valueOf(calendar.get(Calendar.DATE));
Object[] objects={"贵阳",date,"晴朗"};
//只指定应用对象:objects
MessageFormat mf= new MessageFormat("当前时间:{1},地点:{0},天气:{2}");//索引对应于objects元素索引
String result=mf.format(objects);
System.out.println(result);

注意:参数对准!上面只是将字符串填入对应参数位置,而没有用到FormatType、FormatStyle。获取美国时间:

Object[] objects={new Date(),"美国","晴朗"};//指定date或time,传入Date 实例
//只指定应用对象:objects
MessageFormat mf= new MessageFormat("当前时间:{0,time},地点:{1},天气:{2}",Locale.US);
String result=mf.format(objects);
System.out.println(result);

输出为:当前时间:4:00:03 PM,地点:美国,天气:晴朗



四、Spring 中国际化实现

Spring 中国际化是通过实现MessageSource 根接口实现的,继承关系如下:


说明:图中除了标记的接口下面的类中没实现MessageSource 接口中的方法外,其它类都实现或通过继承实现了。

com.springframemork.context.MessageSource 接口中定义的方法:

String getMessage(String code, Object[] args, String defaultMessage, Locale locale);//code即要国际化的信息,args为对象参数数组
String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;


该接口的具体实现细节是在com.springframework.context.support 包中的 MessageSourceSupport 抽象类中实现的。关键代码如下:

package org.springframework.context.support;
...
import org.springframework.util.ObjectUtils;

public abstract class MessageSourceSupport {

	private static final MessageFormat INVALID_MESSAGE_FORMAT = new MessageFormat("");
	protected final Log logger = LogFactory.getLog(getClass());
	private boolean alwaysUseMessageFormat = false;

	private final Map<String, Map<Locale, MessageFormat>> messageFormatsPerMessage =
			new HashMap<String, Map<Locale, MessageFormat>>();				//标记1:定义存储国际化文本信息的结构
	...
	protected String formatMessage(String msg, Object[] args, Locale locale) {			//国际化真正实现函数,被getMessage方法调用
		if (msg == null || (!this.alwaysUseMessageFormat && ObjectUtils.isEmpty(args))) {
			return msg;
		}
		MessageFormat messageFormat = null;							
		synchronized (this.messageFormatsPerMessage) {						//标记2:MessageFormat 类不是同步的,如果多个线程
													//访问同一个Format,要求必须同步。
			Map<Locale, MessageFormat> messageFormatsPerLocale = this.messageFormatsPerMessage.get(msg);
			if (messageFormatsPerLocale != null) {						//标记3:当前msg的国际化信息Set集合还未初始化
				messageFormat = messageFormatsPerLocale.get(locale);
			}
			else {										//标记4:国际化信息Set集合中装入local为键,Message													//-Format为值的Map 集合。
				messageFormatsPerLocale = new HashMap<Locale, MessageFormat>();
				this.messageFormatsPerMessage.put(msg, messageFormatsPerLocale);
			}
			if (messageFormat == null) {
				try {
					messageFormat = createMessageFormat(msg, locale);		//标记5:上面创建结构,此处创建MessageFormat实例
				}
				catch (IllegalArgumentException ex) {
					// invalid message format - probably not intended for formatting,
					// rather using a message structure with no arguments involved
					if (this.alwaysUseMessageFormat) {
						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));			//标记7:调用locale对应的MessageFormat 的format
		}
	}
	protected MessageFormat createMessageFormat(String msg, Locale locale) {			//标记6:返回locale对应的MessageFormat 
		return new MessageFormat((msg != null ? msg : ""), locale);
	}

	protected Object[] resolveArguments(Object[] args, Locale locale) {
		return args;
	}

}


简单实践:

第一步:

配置资源操作类(org.springframework.context.support.ResourceBundleMessageSource或ReloadableResourceBundleMessageSource)bean,指定properties属性配置源文件(即namebases属性)

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
	<property name="basenames">
		<list>
			<value>com/milan/mymain/myProperties</value>
		</list>
	</property>
	<property name="useCodeAsDefaultMessage" value="false"/>  
    	<property name="defaultEncoding" value="UTF-8"/>  
    	<property name="cacheSeconds" value="60"/>  
</bean>


第二步:

编辑myProperties 中程序要访问的属性,其实是MessageFormat 类的参数pattern ,以key=value形式存放:

HelloWorld=Time:{0,date} Hello {1}

第三步:

主函数中使用并运行:

ApplicationContext appContext=new ClassPathXmlApplicationContext("com/milan/mymain/applicationContext.xml");
Object[] object={new Date(),"小明"};
String msg=appContext.getMessage("HelloWorld", object, Locale.US);//HellowWorld 即属性文件中pattern 对应的key。以美国方式显示日期
System.out.println(msg);

结果:





五、Spring 中国际化报错

1、报错日志:

org.springframework.context.NoSuchMessageException: No message found under ...

简单归纳了出现情况:

第一种:

配置 ResourceBundleMessageSource 类的bean 时id 没有设成"messageSource"。可通过更前面的日志记录可知,没有找到id为messageSource的bean时,会使用默认的ReourceBundleMessageSource 对象,我们没有对他进行配置,当然找不到properties 文件。(可以打开ResoureBundleMessageSource类 源码,会发现bean工厂会使用默认的值为messageSource的字符串来查找该类的bean)

第二种:

.properties 文件路径配置出错。查找properties文件默认路径为src 文件夹下,所以如果文件放在包中,要加上包路径(如上面实践)。此外,有的说要在前面加"classpath:",但我加了反而提示错误,估计与spring版本有关。

第三种:

.properties 文件中确实没有定义所要查找的模式字符串pattern 对应的key。


2、学习如何定位错误位置:

简单的错误信息,如缺少包等可直接解决,如果不知错误原因,可仔细查看上面的日志记录,找到与错误相关的日志,查看来源,打开相关类的源码分析。

如上面因为我的bean的id没设成messageSource 而报的错误,可在日志记录找到一条:

[DEBUG] 2017-04-16 19:50:30,005 
method:org.springframework.context.support.AbstractApplicationContext.initMessageSource(AbstractApplicationContext.java:807)
Unable to locate MessageSource with name 'messageSource': using default [org.springframework.context.support.DelegatingMessageSource@2093decc]

分析日志:

提示无法定位messageSource 的bean,所以使用了默认的同样用于国际化的DelegateingMessageSource 类的实例,且能查看到日志来源。打开来源函数,如下:

protected void initMessageSource() {
	ConfigurableListableBeanFactory beanFactory = getBeanFactory();
	if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {					//MESSAGE_SOURCE_BEAN_NAME为方法所属类定义的常量,为messageSource
											//标记2:以MessageSource类形式获取id为messageSource的bean
		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.isDebugEnabled()) {
			logger.debug("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.isDebugEnabled()) {									//标记1:这就是打印日志的位置
			logger.debug("Unable to locate MessageSource with name '" + MESSAGE_SOURCE_BEAN_NAME +
					"': using default [" + this.messageSource + "]");
		}
	}
}


知道了错误原因,ok,改ResourceBundleMessageSource bean的id 为messageSource,之后运行正确!



六、Sping 中实现国际化的各类 接口或类的关系梳理

学习框架,查看源码很有必要,能加深对框架各部分协作关系的理解。自己开始学习spring 时,看到的全是接口,竟然找不到接口中的方法是在哪实现的,如何调用的!

那么Spring 中实现国际化主要涉及到哪些接口或类,它们又是怎么协作的呢?

1、涉及接口或类:(spring-context-***.jar 中)

第一类:

是 com.springframework.context 包中的MessageSource 接口及其子接口或类:MessageSource 中定义了三个getMessage 方法


说明:图中可见,DelegatingMessageSource 实现了MessageSource 接口,找不到我们定义的id应该取名为messageSource 的ResourceBundleMessageSource的bean 时,默认使用的便是该类的实例。

ApplicationContext 实例可以直接调用 getMessage 方法获得国际化后的字符串,但我们可能会更关注getMessage 方法的实现细节。通过eclipse 可发现,getMessage 是在图所示的AbstractMessageSource 抽象类中实现的,但它实际上却是依赖了继承于MessageSourceSupport 类的方法。AbstractMessageSource 声明头:

public abstract class AbstractMessageSource extends MessageSourceSupport implements HierarchicalMessageSource

第二类:

同一jar包的 com.springframework.context.support包中的 MessageSourceSupport 类及其子类:定义了getMessage 方法所依赖的国际化真正实现方法

说明:MssageSourceSupport 关键代码已经在上面 四、Spring 中国际化实现 部分说明了。


2、各部分协作关系

原来画一张清晰的图也好难,随便画画:


说明:getMessage 方法的真正实现是在右边第一、三个框中,所以作为AbstractMessageSource 的子类的 ResourceBundleMessageSource 类也具有了国际化功能。左边的ApplicationContext 的子类就是通过操作 具有国际化功能的这些类而实现国际化的。

那么有个问题,为什么ApplicationContext 还要实现MessageSource 接口呢,它根本没实现真正实现MessageSource 中的getMessage 方法啊。原因是我们并不是直接操作ApplicationContext 的实例,而是将它的子类如 FileSystemXmlApplicationContext、ClassPathXmlApplicationContext 等的实例上转型给ApplicationContext 对象。为了让上转型得到的 ApplicationContext 对象能直接调用getMessage 方法,那么前提是ApplicationContext 接口自身必须要有这个方法,所以必须继承。


Spring 的核心思想就是依赖注入,通过注入接口同时利用接口的多态性来使各部分只关注自己的业务,同时降低耦合。












展开阅读全文

没有更多推荐了,返回首页