1. 程式人生 > >Java —— MessageFormat類 處理國際化

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 [[email protected]decc]

分析日誌:

提示無法定位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 的核心思想就是依賴注入,通過注入介面同時利用介面的多型性來使各部分只關注自己的業務,同時降低耦合。

相關推薦

Java —— MessageFormat 處理國際化

一、MessageFormat 概覽 java.text包中的 Fomart 介面是所有處理格式的基礎介面,有三個子類:DateFormat、MessageFormat、NumberFormat。 MessageFormat 是專門處理文字格式的類,且沒有子類。 二、M

Java 使用BigDecimal處理高精度計算

positive urn 使用 println highlight 轉換 posit exception val Java在java.math包中提供的API類BigDecimal,用來對超過16位有效位的數進行精確的運算。雙精度浮點型變量double可以處理16位有效數,

java中小數處理,bigDecimal應用

add opened 對象 spl eof 轉換成 () double ply 1、構造一個BigDecimal對象: 1 //創建一個BigDecimal對象,初始化必須使用字符串,因為用數值初始化會得到近似值,不準確 2 BigDec

java基礎庫學習(四.2)異常處理的陷阱

前言 java的異常類Exception繼承自非正常情況類Throwable,異常類下又分為兩大類:checked異常和runtime異常, 其中發生checked異常的類如果不做處理程式會發生編譯錯誤,導致程式中斷編譯 而runtime異常的類只有在執行階段才會發生,如果不做處理,

Java 常用之異常處理

Java異常類是對於程式中可能出現的錯誤或者異常的一種處理方式。在設計程式的過程中,對於可能出現的異常錯誤,比如說使用者輸入錯誤,裝置錯誤,磁碟滿了或者程式碼錯誤等等,通常採用異常處理的方式來進行處理可能的錯誤。 JAVA的異常處理機制:如果某個方法不能按照正常的途徑完成任務,就可以通過另一

Apache PDFBox 2.0.13 釋出,Java 的 PDF 處理

   Apache PDFBox 2.0.13 已釋出,這是針對 2.0.12 版本的 Bug 修復版本,包含一些修復和小改進。 部分更新內容如下: [PDFBOX-4335] - Overlay should implement Closeable [PDFBOX-43

java中子繼承父和實現介面有同名方法怎麼處理

Java是一門單繼承語言,但是,在子類的繼承關係中,會存在父類和介面有同名方法的情況,這種情況該怎麼處理呢? 我們來一步步驗證: 1.子類繼承父類,實現介面,父類和介面有同名方法 public int

JAVA工具分享之《JSON處理:JsonUtil》

前言 今天給大家分享的是操作json的工具類,使用的是jackson,如果你使用的是spring boot的話直接引入spring-boot-starter-parent響應的包會自動引入。 <parent> <

java中Excel處理工具

/** 該工具類會返回處理結果和封裝之後的資料,獲取資料直接從 **/ import java.io.IOException; import java.io.InputStream; import java.text.DecimalFormat; import ja

java中自定義使用{0}佔位符功能之MessageFormat

MessageFormat提供一種語言無關的方式來組裝訊息,它允許你在執行時刻用指定的引數來替換掉訊息字串中的一部分。你可以為MessageFormat定義一個模式,在其中你可以用佔位符來表示變化的部分,例如在下面的測試類中: package cn.lz.life.uti

JAVA工具時間處理DateUtils

廢話不說,直接上碼: package util; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calenda

Java中最好使用BigDecimal處理加減乘除運算

Java中,對於不需要任何準確計算精度的數字可以直接使用float或double運算,但是如果需要精確計算的結果,則必須使用類,而且使用類也可以進行大數的操作。ava.math.BigDecimal。BigDecimal一共有4個夠造方法,讓我先來看看其中的兩種用法:第一種:

JAVA 數字格式化處理方法; 國際化

有時我們需要控制輸出的數字的格式,如何使用java的類庫做到這個呢?例如數字“1234.56”如何以“1234.560”、“1,234.56”格式輸出,在此你可以找到答案例子:例如數字:1、1234.56以1234.560格式輸出DecimalFormat df1 = new

Java的數字處理

數字格式化:Java主要對浮點型資料進行數字格式化操作,其中浮點型包括float(單精度)型和double(雙精度)型,在 Java中使用Java.text.DecimalFormat格式化數字;    在Java中沒有格式化的數字遵循以下原則;        1.如果資料絕

JAVA高階應用之異常處理

異常分類 Throwable(異常最頂端的類) Error(伺服器奔潰 資料庫奔潰) Exception(異常類) RunTimeException 解決異常

Java 資料精確度處理&Mach

Math.rint()   a = Math.rint(a); 返回最接近引數的整數,如果有2個數同樣接近,則返回偶數的那個。它有兩個特殊的情況:1)如果引數本身是整數,則返回本身。2)如果不是數字或無窮大或正負0,則結果為其本身。 Math.round() a = Ma

Java之異常處理與工具

異常處理 ①   Error(錯誤) Error(錯誤) 一般指比較嚴重的問題,不做針對性處理,無法挽救;            OutOfMemoryError  記憶體溢位 ②   Exc

課堂動手動腦驗證以及自定義異常實現對異常處理——java異常

異常(exception):發生在程式執行期間,表明出現了一個非法執行的情況。許多JDK中的方法在檢測到非法情況時,都會丟擲一個異常物件。例如:陣列越界和被0除。 程式碼驗證: package test; import javax.swing.*; class AboutException { p

java工具,在Windows,Linux系統獲取電腦的MAC地址、本地IP、電腦名

copy iter 去掉m [] equals linu stat cli catch package com.cloudssaas.util; import java.io.BufferedReader; import java.io.IOException;

轉載--創建java常量的方法

ret get ntc www 引用 lsi public field log 1 /** 2 * Method One 3 */ 4 interface ConstantInterface { 5 String SUNDAY