天天看點

Struts2中的參數傳遞

本篇主要通過執行個體來講述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類型的資料進行轉化。 

Struts2中的參數傳遞

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);  

}  

有了這個類,我們就可以比較輕松的對枚舉類型進行資料指派了。 

Struts2中的參數傳遞

public enum gender {  

    male, female  

Struts2中的參數傳遞

<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>  

Struts2中的參數傳遞

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下: 

Struts2中的參數傳遞

java.lang.enum=com.opensymphony.xwork2.util.enumtypeconverter  

對于使用新的版本的xwork的朋友,則不需要增加這個配置檔案。 

date類型 

xwork預設是支援date類型的轉化的。不過從源碼上來看,貌似我們很難用上它預設的類型轉化。 

Struts2中的參數傳遞

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的代碼示例: 

Struts2中的參數傳遞

<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" />  

Struts2中的參數傳遞

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. 在配置檔案上傳時,攔截器的順序非常關鍵 

Struts2中的參數傳遞

<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的特性,将這些檔案和檔案名等檔案資訊做封裝: 

Struts2中的參數傳遞

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在運作時,能夠正确設定上傳的檔案名。 

Struts2中的參數傳遞

/** 

 * @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的基礎知識的時候列出來過的一個接口嘛? 

Struts2中的參數傳遞

 * 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的接口定義: 

Struts2中的參數傳遞

 *  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: 

Struts2中的參數傳遞

在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

我在這裡提供一個我在實際項目中采用的方式: 

Struts2中的參數傳遞

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]