Java —— MessageFormat类 处理国际化

一、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 声明头:

 

<span style="font-size:12px;">public abstract class AbstractMessageSource extends MessageSourceSupport implements HierarchicalMessageSource</span>  


第二类:

同一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 的核心思想就是依赖注入,通过注入接口同时利用接口的多态性来使各部分只关注自己的业务,同时降低耦合。

posted on 2018-06-01 14:45  腾飞的鹰  阅读(762)  评论(0)    收藏  举报

导航