本篇主要通過執行個體來講述struts2中各種各樣的參數傳遞。這個參數傳遞的過程主要指資料從view層傳遞到control層時struts2的工作方式。根據前兩篇文章的知識,我們知道,struts2完成參數傳遞處理工作的基礎是ognl和valuestack。而在這個過程中,我也把struts2所要做的工作大緻歸納為兩個方面:
1. 對ognl操作進行封裝,完成ognl表達式所表示的值到java對象的值傳遞機制
2. 在參數傳遞的過程中,做恰當的類型轉化,保證頁面上的字元串能夠轉化成各式各樣的java對象
接下來,通過四個不同的角度,來具體講述struts2在這兩個方面的工作。
最簡單的參數傳遞
使用ognl的最基本的功能,就能完成普通的java對象的指派工作。struts2在内部已經完成了ognl的基本封裝。這些封裝包括對ognl表達式到java對象的指派機制,以及對基本的java類型的類型轉化支援。這些基本類型包括string、number(以及其基本類型int、float、double等)、boolean(boolean)、數組、class、date等類型。
在這裡我想額外強調的是xwork對jdk5.0中的enum類型和date類型的支援。
enum類型
枚舉類型是jdk5.0引入的新特性。枚舉類型也能解決很多實際問題,是j2ee程式設計中的最佳實踐之一。xwork中,有一個專門的enumtypeconverter負責對enum類型的資料進行轉化。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5ichR3cf52bjl2LcNXZnFWbp9CXt92YuUWelRXauIXdvBnb39GZvw1LcpDc0RHaiojIsJye.png)
public class enumtypeconverter extends defaulttypeconverter {
/**
* converts the given object to a given type. how this is to be done is implemented in toclass. the ognl context, o
* and toclass are given. this method should be able to handle conversion in general without any context or object
* specified.
*
* @param context - ognl context under which the conversion is being done
* @param o - the object to be converted
* @param toclass - the class that contains the code to convert to enumeration
* @return converted value of type declared in toclass or typeconverter.noconversionpossible to indicate that the
* conversion was not possible.
*/
public object convertvalue(map context, object o, class toclass) {
if (o instanceof string[]) {
return convertfromstring(((string[]) o)[0], toclass);
} else if (o instanceof string) {
return convertfromstring((string) o, toclass);
}
return super.convertvalue(context, o, toclass);
}
* converts one or more string values to the specified class.
* @param value - the string values to be converted, such as those submitted from an html form
* @param toclass - the class to convert to
* @return the converted object
public java.lang.enum convertfromstring(string value, class toclass) {
return enum.valueof(toclass, value);
}
有了這個類,我們就可以比較輕松的對枚舉類型進行資料指派了。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5ichR3cf52bjl2LcNXZnFWbp9CXt92YuUWelRXauIXdvBnb39GZvw1LcpDc0RHaiojIsJye.png)
public enum gender {
male, female
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5ichR3cf52bjl2LcNXZnFWbp9CXt92YuUWelRXauIXdvBnb39GZvw1LcpDc0RHaiojIsJye.png)
<form method="post" action="/struts-example/enum-conversion.action">
<input type="text" name="user.name" value="downpour" />
<select name="user.gender">
<option value="male">男</option>
<option value="female">女</option>
</select>
<input type="submit" value="submit" />
</form>
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5ichR3cf52bjl2LcNXZnFWbp9CXt92YuUWelRXauIXdvBnb39GZvw1LcpDc0RHaiojIsJye.png)
public class enumconversionaction extends actionsupport {
private static final log logger = logfactory.getlog(policy.class);
private user user;
/* (non-javadoc)
* @see com.opensymphony.xwork2.actionsupport#execute()
@override
public string execute() throws exception {
logger.info("user's gender:" + user.getgender());
return super.execute();
// setters and getters
通過上面的代碼,就完成了對枚舉類型的指派。不過這裡有一點需要特别指出:那就是xwork在xwork-2.1.x的版本之前,枚舉類型不被預設支援。如果你需要獲得枚舉類型的自動指派,還需要增加一個配置檔案xwork-conversion.properties到classpath下:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5ichR3cf52bjl2LcNXZnFWbp9CXt92YuUWelRXauIXdvBnb39GZvw1LcpDc0RHaiojIsJye.png)
java.lang.enum=com.opensymphony.xwork2.util.enumtypeconverter
對于使用新的版本的xwork的朋友,則不需要增加這個配置檔案。
date類型
xwork預設是支援date類型的轉化的。不過從源碼上來看,貌似我們很難用上它預設的類型轉化。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5ichR3cf52bjl2LcNXZnFWbp9CXt92YuUWelRXauIXdvBnb39GZvw1LcpDc0RHaiojIsJye.png)
private object doconverttodate(map 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,
simpledateformat dfmt = (simpledateformat) dateformat.getdateinstance(dateformat.short,
simpledateformat[] fmts = {fullfmt, dtfmt, dfmt};
for (int i = 0; i < fmts.length; i++) {
try {
check = fmts[i].parse(sa);
df = fmts[i];
if (check != null) {
break;
}
} catch (parseexception ignore) {
}
}
} else if (java.util.date.class == totype) {
simpledateformat d1 = (simpledateformat) dateformat.getdatetimeinstance(dateformat.short, dateformat.long, locale);
simpledateformat d2 = (simpledateformat) dateformat.getdatetimeinstance(dateformat.short, dateformat.medium, locale);
simpledateformat d3 = (simpledateformat) dateformat.getdatetimeinstance(dateformat.short, dateformat.short, locale);
simpledateformat rfc3399 = new simpledateformat("yyyy-mm-dd't'hh:mm:ss");
simpledateformat[] dfs = {d1, d2, d3, rfc3399}; //added rfc 3339 date format (xw-473)
for (int i = 0; i < dfs.length; i++) {
check = dfs[i].parse(sa);
df = dfs[i];
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)) {
constructor constructor = totype.getconstructor(new class[]{long.class});
return constructor.newinstance(new object[]{new long(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;
這段代碼就是xwork處理将string轉成date類型的過程,從整個過程上來看,我們很難用上這段代碼,因為我們在界面上的date類型的表現形式往往是:'yyyy-mm-dd'或者相關的形式,很明顯,上面的流程無法比對這樣的日期類型。
是以,針對date,我們往往會自定義一個日期轉化的類進行處理,這個在下面會有具體的介紹。
array、list、map等容器類型的參數傳遞
除了簡單的基于javabean方式的參數傳遞支援,struts2還支援對array、list、map等容器類型的資料結構做資料指派。不過曆史一路走來,xwork針對容器類型的資料指派一直有變化,讓我們慢慢解讀這些變化,進而也來看看程式設計思路是如何改變的。
1. 2004年,xwork-1.0.x的年代
總的來說,那個年代對于容器的資料指派,需要依賴于xwork的輔助類。我們可以看到,如果你要對list進行指派,需要建立一個xworklist的實作類,并把所需要進行資料指派的java類傳遞到xworklist的構造函數中。而對map等對象的指派,也同理可得。
這種資料指派的方式的優缺點都非常明顯。優點在于簡單,你不需要額外定義任何其他的内容,而是直接使用xwork的輔助類來實作類型轉化。缺點在于擴充性很弱,很明顯,針對某一個具體的容器,就需要一個xwork的實作類,list有xworklist對應,map有xworkmap對應。甚至在那個時候,還沒有set的支援,因為沒有xworkset的實作。是以使用這種方式,在擴充性方面需要遭受嚴重的考驗。
2. 2006年,xwork-2.0.x的年代
不過這個新的整合方式似乎并不被大家所看好。
lllyq 寫道
我覺得xworklist, xworkmap還是很有用的,挺好的設計,其實沒有必要deprecated。
moxie 寫道
集合支援不向下相容。xworklist已經是@deprecated,用它就錯,還不如直接删除掉。在webwork2.2中,它需要為集合另外配置一個conversion.properties檔案。真不明白,這樣有什麼優點?
這種新的整合方式,實際上隻是解決了針對容器指派,不需要依賴xwork的輔助類這樣的一個問題,不過其付出的代價,卻是多了一個配置檔案,這也讓人非常郁悶。好好的類型轉化,平白無故多出了一個同package下的配置檔案,這也無形中增加了程式設計的複雜度。
3. 現在,擁抱了泛型和annotation的年代
實際上,在xwork發展到xwork-2.0.x之後,也開始注重了對泛型和annotation的支援。是以,容器類型的轉化,我們也可以嘗試一下使用jdk的新特性來進行,當然這也是目前最為推薦的做法。
下面分别給出使用泛型和annotation的代碼示例:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5ichR3cf52bjl2LcNXZnFWbp9CXt92YuUWelRXauIXdvBnb39GZvw1LcpDc0RHaiojIsJye.png)
<form method="post" action="/struts-example/ognl-collection-conversion.action">
<input type="text" name="users[0].name" value="aaa" />
<input type="text" name="users[1].name" value="bbb" />
<input type="text" name="users2[0].name" value="ccc" />
<input type="text" name="users2[1].name" value="ddd" />
<input type="text" name="usermap['user1'].name" value="eee" />
<input type="text" name="usermap['user2'].name" value="fff" />
<input type="text" name="usermap2['user3'].name" value="ggg" />
<input type="text" name="usermap2['user4'].name" value="hhh" />
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5ichR3cf52bjl2LcNXZnFWbp9CXt92YuUWelRXauIXdvBnb39GZvw1LcpDc0RHaiojIsJye.png)
public class ognlconversionaction extends actionsupport {
private static final long serialversionuid = 4396125455881691845l;
private list<user> users;
@element(value = user.class)
private list users2;
private map<string, user> usermap;
private map usermap2;
// -> aaa
logger.info("users[0].name : " + users.get(0).getname());
// -> bbb
logger.info("users[1].name : " + users.get(1).getname());
// -> ccc
logger.info("users2[0].name : " + ((user)users2.get(0)).getname());
// -> ddd
logger.info("users2[1].name : " + ((user)users2.get(1)).getname());
// -> [user1, user2]
logger.info("usermap.key : " + usermap.keyset());
// -> eee
logger.info("usermap.key = " + "user1" + " : " + "usermap.value(user1's name) = " + usermap.get("user1").getname());
// -> fff
logger.info("usermap.key = " + "user2" + " : " + "usermap.value(user2's name) = " + usermap.get("user2").getname());
// -> [user3, user4]
logger.info("usermap2.key : " + usermap2.keyset());
// -> ggg
logger.info("usermap2.key = " + "user3" + " : " + "usermap.value(user3's name) = " + ((user)usermap2.get("user3")).getname());
// -> hhh
logger.info("usermap2.key = " + "user4" + " : " + "usermap.value(user4's name) = " + ((user)usermap2.get("user4")).getname());
上面的代碼中,我們可以看到,如果你使用泛型,那麼你無需再使用任何額外的配置檔案或者annotation,xwork會把一切都為你準備好。如果你沒有使用泛型,那麼你可以使用annotation來指定你需要進行轉化的對象類型。其中,對map對象使用annotation時,element中的value所對應的值,是map中的value所對應的class。
由此可見,泛型和annotation,在一定程度上,還是可以簡化我們很多工作的。
檔案上傳
檔案上傳其實也是參數傳遞的一種,是以從方案上來講,struts2同樣使用了一個攔截器來處理。而這個攔截器,同樣來自于原來的webwork,基本上沒有做什麼很大的改變。有關這個攔截器的詳細内容,我們也留一個懸念,在後續章節中詳細講解。目前,你隻要知曉,這個攔截器能幫助你完成一切檔案上傳相關的機制。
在這裡我簡單小結一下在進行檔案上傳時的三大要點:
1. 在配置檔案上傳時,攔截器的順序非常關鍵
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5ichR3cf52bjl2LcNXZnFWbp9CXt92YuUWelRXauIXdvBnb39GZvw1LcpDc0RHaiojIsJye.png)
<interceptor-stack name="uploadstack">
<interceptor-ref name="upload"/>
<interceptor-ref name="defaultstack"/>
</interceptor-stack>
具體來說,upload的攔截器,必須在params的攔截器之前
2. 攔截器額外提供了一些額外的檔案資訊
quake wang 寫道
contenttype: 檔案的contenttype(可以用在做download的時候)
filename: 實際的檔案名
在上面的action例子裡, 那麼有uploadfilescontenttype和uploadfilesfilename這2個屬性, 也能夠被自動綁定
3. 攔截器提供的檔案上傳功能,你得到的是一個臨時檔案
robbin 寫道
在webwork的file upload 攔截器功能中,它提供的file隻是一個臨時檔案,action執行之後就會被自動删除,是以你必須在action中自己出來檔案的存儲問題,或者寫到伺服器的某個目錄,或者儲存到資料庫中。如果你準備寫到伺服器的某個目錄下面的話,你必須自己面臨着處理檔案同名的問題
而時代發展到struts2的年代,對于檔案上傳的整體機制沒有做什麼改變。隻是struts2将apache的common-fileupload作為了其預設的檔案上傳的機制。
例子歸例子,實際情況中,我們還是會遇到一些問題:
1. 預設實作中,檔案和檔案資訊是分開表述的,對于背景處理來說,不是非常友善。
2. common-fileupload的實作,雖然提供了檔案上傳的機制,也可以讓你得到檔案的一些屬性資訊,但是它無法得到用戶端的上傳路徑
對于第一個問題,我們可以使用ognl的特性,将這些檔案和檔案名等檔案資訊做封裝:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5ichR3cf52bjl2LcNXZnFWbp9CXt92YuUWelRXauIXdvBnb39GZvw1LcpDc0RHaiojIsJye.png)
public class filecomponent implements serializable {
private static final long serialversionuid = 4594003082271182188l;
private file upload;
private string filename;
* the default constructor
public filecomponent() {
* @return returns the upload.
public file getupload() {
return upload;
* @return returns the filename.
public string getfilename() {
return filename;
* @param upload
* the upload to set.
public void setupload(file upload) {
this.upload = upload;
* @param filename
* the filename to set.
public void setfilename(string filename) {
this.filename = filename;
public void setuploadfilename(string uploadfilename) {
this.filename = uploadfilename;
在這個類中,我定義了upload表示上傳的檔案,filename表示上傳檔案的檔案名。請注意我整個檔案中的最後一個方法:setuploadfilename。這個方法将保證fileuploadinterceptor在運作時,能夠正确設定上傳的檔案名。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5ichR3cf52bjl2LcNXZnFWbp9CXt92YuUWelRXauIXdvBnb39GZvw1LcpDc0RHaiojIsJye.png)
/**
* @param filename
* the filename to set.
*/
public void setuploadfilename(string uploadfilename) {
this.filename = uploadfilename;
這樣,在action中,我們将面對一個個完整的filecomponent對象,其中包括檔案的引用、檔案名稱和其他檔案資訊。這樣就不會因為上傳多個檔案而手足無措,你隻需要使用filecomponent數組,就能輕松對上傳的檔案進行管理,而避免了在action中書寫許多個檔案、檔案名等屬性了。
對于第二個問題,目前我也沒有找到很好的方法。我所采用的方式與yulimin是一緻的:
yulimin 寫道
我現在的做法是表單中增加了一個隐藏域,當使用者檔案選擇後,利用js截取到使用者選擇的檔案名,然後一起送出上去。
不知道有沒有最終的解決方法?
自定義的類型轉化實作
struts2在處理參數傳遞的過程中,需要完成類型轉化,保證頁面上的字元串能夠轉化成各式各樣的java對象。而這一點,其實也是由ognl完成的。還記得我們在講述ognl的基礎知識的時候列出來過的一個接口嘛?
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5ichR3cf52bjl2LcNXZnFWbp9CXt92YuUWelRXauIXdvBnb39GZvw1LcpDc0RHaiojIsJye.png)
* appends the standard naming context for evaluating an ognl expression
* into the context given so that cached maps can be used as a context.
*
* @param root the root of the object graph
* @param context the context to which ognl context will be added.
* @return context map with the keys <code>root</code> and <code>context</code>
* set appropriately
public static map adddefaultcontext( object root, classresolver classresolver, typeconverter converter, memberaccess memberaccess, map context );
在這個接口中,我們可以在使用ognl的時候,注冊針對某個class級别的自己實作的typeconverter,這樣,ognl就會在進行設值計算和取值計算的時候,使用自定義的類型轉化方式了。讓我們來看看typeconverter的接口定義:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5ichR3cf52bjl2LcNXZnFWbp9CXt92YuUWelRXauIXdvBnb39GZvw1LcpDc0RHaiojIsJye.png)
* context - ognl context under which the conversion is being done
* target - target object in which the property is being set
* member - member (constructor, method or field) being set
* propertyname - property name being set
* value - value to be converted
* totype - type to which value is converted
public object convertvalue(map context, object target, member member, string propertyname, object value, class totype);
知道了原理,就簡單了,我們可以自己實作一個typeconverter的實作類,并且在struts2中注冊一下使用這個typeconverter的java類型的對應關系,我們就可以完成自定義的類型轉化了。
不過針對quake wang的例子,我也想做一些補充。它的例子中,主要講述了struts2中如何去做java.utils.date的自動類型轉化,也正如後面回複中有人提到:
wolfsquare 寫道
如果我在界面上有兩種格式的日期怎麼辦?
例如一種短格式: simpledateformat("yyyy-mm-dd"),一種長格式simpledateformat("yyyy-mm-dd hh:mm:ss")
而quake wang對此是這樣解決的:
可以根據你的應用情況,看哪種方式是比較常見的轉換規則,那麼把這個規則定成application-wide conversion rules:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5ichR3cf52bjl2LcNXZnFWbp9CXt92YuUWelRXauIXdvBnb39GZvw1LcpDc0RHaiojIsJye.png)
在classpath root下面寫一個xwork-conversion.properties:
java.util.date=com.javaeye.core.webwork.converter.dateconverter
另外的一個轉換,可以寫成class-specific conversion rules :
otherdate=com.javaeye.core.webwork.converter.otherdateconverter
我在這裡提供一個我在實際項目中采用的方式:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5ichR3cf52bjl2LcNXZnFWbp9CXt92YuUWelRXauIXdvBnb39GZvw1LcpDc0RHaiojIsJye.png)
public class dateconverter extends defaulttypeconverter {
private static final log logger = logfactory.getlog(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
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);
* convert string to date
*
* @param value
* @return
private date doconverttodate(object value) {
if (value instanceof string) {
// todo add date converter parse order here
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);
* convert date to string
private string doconverttostring(object value) {
simpledateformat simpledateformat = new simpledateformat(datetime_pattern);
string result = null;
if (value instanceof date) {
result = simpledateformat.format(value);
在我采用的方式中,依然采用application-wide conversion rules,隻是在自定義的converter中,根據不同的日期形式進行逐個比對,找到第一個比對的日期類型進行轉化。當然,這是一種投機取巧的辦法,不過比較實用,适用于項目中不太使用國際化,日期的形式不太複雜的情況。
原文連結:[http://wely.iteye.com/blog/2295297]