很久沒有寫Blog了,最近彷佛變懶了。今天上班沒有不少事,因而把以前遇到的一個問題記錄下來。
Web開發會涉及到不少類型轉換的狀況。咱們知道,頁面中的一切值都是字符串類型,而到後臺,咱們須要的多是其餘各類類型;同時,頁面顯示也是字符串類型。這就涉及到Web中基本的類型轉換問題:從String轉換爲各類類型與從各類類型轉換爲String類型。
在Java Web開發中,進行上述轉換通常有如下幾種:
一、在Servlet中,這一切的轉換咱們得本身寫代碼完成;
二、在Struts1.x中,咱們經過apache commons-beanutils中的converters來幫助完成這些事情;
三、在Struts2中,使用的則是基於ongl的類型轉換;
……
因爲類型轉換的通用性,於是Web框架都會實現大多數類型的轉換功能,而不須要程序員編碼實現。然而,對於java.util.Date這種類型的轉換,各大框架彷佛作得都不盡如人意。如:在Struts1.x中,該類型的轉換就會有問題,不少人建議使用java.sql.Date這種類型來解決日期轉換的問題(實際上能夠自定義一個類型轉換器來解決該問題)。在Struts2中,這個問題彷佛依然存在,也許你歷來沒有遇到過。的確,通常人確實不會遇到,會以爲沒有這個問題。下面就是我遇到的問題與解決方法。
日期類型的轉換,Web開發中幾乎都會遇到,我如今作得項目也不例外。在開發的過程當中,也許就像你同樣,我沒有對日期類型的轉換作任何特殊的處理,並且Struts2也很好的幫我完成了轉換。然而同事測試的時候卻出現了一個「莫名其妙」的問題:輸入一個經常使用格式的日期類型yyyy-MM-dd,到後臺卻報錯:找不到對應的set方法——setEffDate(Ljava.lang.String)。的確,程序中只有setEffDate(java.util.Date)這個方法,沒有setEffDate(Ljava.lang.String)這個方法。從Ljava.lang.String能夠看出,傳到後臺的String類型並無轉換成Date類型,於是報錯。
一開始,我覺得是我UT沒作好,因而在本身的電腦上模擬同事的測試,結果一點問題也沒有。這就奇怪了。通過本身分析,以爲多是IE瀏覽器的緣由,由於同事測試用的是IE,而我用的是FireFox。因而在本身的機子上用IE測試,同時在同事機子上用FireFox測試,結果這兩次測試都沒有出現上面的問題。雖然沒有找到問題所在,但能夠初步確定:IE的問題,但彷佛又不徹底是IE的問題,由於在個人電腦上的IE(版本與同事同樣,都是IE6)沒有上述問題。這就奇怪了,是什麼問題呢,真是百思不得其解。
這個時候,我想起了以前遇到的一個不解得狀況:從後臺得到的日期類型在頁面上顯示時,跟上面狀況同樣,在同事的IE中,日期顯示的格式居然是:yyyy-MM-ddTHH:mm:ss。多了一個T,真是莫名其妙,並且只在同事的IE瀏覽器中出現(當時解決方法是在JS中將'T'替換爲空格,沒有去深究,但如今必須的深究了)。yyyy-MM-ddTHH:mm:ss這種日期格式有嗎?因而查詢JDK:在SimpleDateFormat類中找到了該日期格式,這種格式是「美國語言環境中日期和時間的模式之一」。原來還真有這種格式。居然這是美國語言中使用的日期格式,而Struts2是美國人開發的,也許跟這個有點關係。因而查看Struts2中關於Date類型轉換的源碼。
在XWorkBasicConverter類中
private Object doConvertToDate(Map<String, Object> context, Object value, Class toType) {
Date result =
null;
if (value
instanceof String && value !=
null && ((String) value).length() > 0) {
String sa = (String) value;
Locale locale = getLocale(context);
DateFormat df =
null;
if (java.sql.Time.
class == toType) {
df = DateFormat.getTimeInstance(DateFormat.MEDIUM, locale);
}
else
if (java.sql.Timestamp.
class == toType) {
Date check =
null;
SimpleDateFormat dtfmt = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT,
DateFormat.MEDIUM,
locale);
SimpleDateFormat fullfmt =
new SimpleDateFormat(dtfmt.toPattern() + MILLISECOND_FORMAT,
locale);
SimpleDateFormat dfmt = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT,
locale);
SimpleDateFormat[] fmts = {fullfmt, dtfmt, dfmt};
for (SimpleDateFormat fmt : fmts) {
try {
check = fmt.parse(sa);
df = fmt;
if (check !=
null) {
break;
}
}
catch (ParseException ignore) {
}
}
}
else
if (java.util.Date.
class == toType) {
Date check =
null;
DateFormat[] dfs = getDateFormats(locale);
for (DateFormat df1 : dfs) {
try {
check = df1.parse(sa);
df = df1;
if (check !=
null) {
break;
}
}
catch (ParseException ignore) {
}
}
}
//final fallback for dates without time
if (df ==
null) {
df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
}
try {
df.setLenient(
false);
// let's use strict parsing (XW-341)
result = df.parse(sa);
if (!(Date.
class == toType)) {
try {
Constructor constructor = toType.getConstructor(
new Class[]{
long.
class});
return constructor.newInstance(
new Object[]{Long.valueOf(result.getTime())});
}
catch (Exception e) {
throw
new XWorkException(
"Couldn't create class " + toType +
" using default (long) constructor", e);
}
}
}
catch (ParseException e) {
throw
new XWorkException(
"Could not parse date", e);
}
}
else
if (Date.
class.isAssignableFrom(value.getClass())) {
result = (Date) value;
}
return result;
}
private DateFormat[] getDateFormats(Locale locale) {
DateFormat dt1 = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG, locale);
DateFormat dt2 = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, locale);
DateFormat dt3 = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale);
DateFormat d1 = DateFormat.getDateInstance(DateFormat.SHORT, locale);
DateFormat d2 = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);
DateFormat d3 = DateFormat.getDateInstance(DateFormat.LONG, locale);
DateFormat rfc3399 =
new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss");
DateFormat[] dfs = {dt1, dt2, dt3, rfc3399, d1, d2, d3};
//added RFC 3339 date format (XW-473)
return dfs;
}
其中SHORT、MEDIUM、LONG在JDK中的DateFormat類中有說明。
從這句DateFormat rfc3399 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");能夠看出,Struts2硬編碼使用了這樣一種格式。然而,Struts2中這種格式是放在最後的,爲啥只有同事的IE瀏覽器測試時使用的是這種格式呢?(在調試時,用同時IE,日期輸入中按這種格式輸入,還真的沒有問題了)這說明,同時的電腦中,前面六種DateFormat都沒有匹配,查看DateFormat中關於SHORT、MEDIUM、LONG的說明,能夠知道,對於yyyy-MM-dd這種日期類型,在英語語言中是無法匹配的,因爲Struts2匹配日期時,使用了Locale,可見,同事的IE瀏覽器默認的語言環境是英語。一經查看,果真如此,把中文設置爲默認語言環境,再測試,沒問題了。終於知道了緣由。
我的以爲,Struts2中,最後一種日期模式寫死成美國標準,不是很好。
針對這個問題,咱們無法要求客戶必定設置中文爲默認瀏覽器的語言環境。於是對於Date類型的轉換,能夠本身定義一個轉換器。如下來自http://www.javaeye.com/wiki/struts2/1365-passing-parameters-in-struts2 中的一個類型轉換器定義(不適合國際化的環境),如須要,你能夠定義本身的轉換器:
public
class DateConverter
extends DefaultTypeConverter {
private
static
final Logger logger = Logger.getLogger(DateConverter.
class);
private
static
final String DATETIME_PATTERN =
"yyyy-MM-dd HH:mm:ss";
private
static
final String DATE_PATTERN =
"yyyy-MM-dd";
private
static
final String MONTH_PATTERN =
"yyyy-MM";
/**
* Convert value between types
*/
@SuppressWarnings(
"unchecked")
public Object convertValue(Map ognlContext, Object value, Class toType) {
Object result =
null;
if (toType == Date.
class) {
result = doConvertToDate(value);
}
else
if (toType == String.
class) {
result = doConvertToString(value);
}
return result;
}
/**
* Convert String to Date
*
* @param value
* @return
*/
private Date doConvertToDate(Object value) {
Date result =
null;
if (value
instanceof String) {
result = DateUtils.parseDate((String) value,
new String[] { DATE_PATTERN, DATETIME_PATTERN, MONTH_PATTERN });
// all patterns failed, try a milliseconds constructor
if (result ==
null && StringUtils.isNotEmpty((String)value)) {
try {
result =
new Date(
new Long((String) value).longValue());
}
catch (Exception e) {
logger.error(
"Converting from milliseconds to Date fails!");
e.printStackTrace();
}
}
}
else
if (value
instanceof Object[]) {
// let's try to convert the first element only
Object[] array = (Object[]) value;
if ((array !=
null) && (array.length >= 1)) {
value = array[0];
result = doConvertToDate(value);
}
}
else
if (Date.
class.isAssignableFrom(value.getClass())) {
result = (Date) value;
}
return result;
}
/**
* Convert Date to String
*
* @param value
* @return
*/
private String doConvertToString(Object value) {
SimpleDateFormat simpleDateFormat =
new SimpleDateFormat(DATETIME_PATTERN);
String result =
null;
if (value
instanceof Date) {
result = simpleDateFormat.format(value);
}
return result; } }
能夠將該轉換器註冊爲全局的:在classpath下創建xwork-conversion.properties文件,內容爲:java.util.Date=你的類型轉換器的完整限定類名 自此,該問題圓滿解決。