程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Struts2中的參數傳遞

Struts2中的參數傳遞

編輯:關於JAVA

我們知道,Struts2完成參數傳遞處理工作的基礎是OGNL和ValueStack。而在這個過程中,我也把Struts2所要做的工作大致歸納為兩個方面:

1. 對OGNL操作進行封裝,完成OGNL表達式所表示的值到Java對象的值傳遞機制

2. 在參數傳遞的過程中,做恰當的類型轉化,保證頁面上的字符串能夠轉化成各式各樣的Java對象

接下來,通過四個不同的角度,來具體講述Struts2在這兩個方面的工作。

目 錄 [ - ]

最簡單的參數傳遞

Array、List、Map等容器類型的參數傳遞

文件上傳

自定義的類型轉化實現

最簡單的參數傳遞

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

有了這個類,我們就可以比較輕松的對枚舉類型進行數據賦值了。

Java代碼

public enum Gender {

   MALE, FEMALE
}

Html代碼

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

Java代碼

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

Java代碼

java.lang.Enum=com.opensymphony.xwork2.util.EnumTypeConverter

對於使用新的版本的XWork的朋友,則不需要增加這個配置文件。

Date類型

XWork默認是支持Date類型的轉化的。不過從源碼上來看,貌似我們很難用上它默認的類型轉化。

Java代碼

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,
             locale);
         SimpleDateFormat dfmt = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT,
             locale);
         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) {
         Date check = null;
         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++) {
           try {
             check = dfs[i].parse(sa);
             df = dfs[i];
             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[]{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所支持的針對容器的數據賦值還比較土。這方面moxie在論壇上有一篇文章專門來講述:http://www.javaeye.com/topic/8770。

總的來說,那個年代對於容器的數據賦值,需要依賴於XWork的輔助類。我們可以看到,如果你要對List進行賦值,需要新建一個XWorkList的實現類,並把所需要進行數據賦值的Java類傳遞到XWorkList的構造函數中。而對Map等對象的賦值,也同理可得。

這種數據賦值的方式的優缺點都非常明顯。優點在於簡單,你不需要額外定義任何其他的內容,而是直接使用XWork的輔助類來實現類型轉化。缺點在於擴展性很弱,很明顯,針對某一個具體的容器,就需要一個XWork的實現類,List有XWorkList對應,Map有XWorkMap對應。甚至在那個時候,還沒有Set的支持,因為沒有XWorkSet的實現。所以使用這種方式,在擴展性方面需要遭受嚴重的考驗。

2. 2006年,XWork-2.0.X的年代

也許是XWork團隊看到了擴展性上的問題,所以在XWork和Webwork同時升級以後,采用了新的方式來處理容器賦值。而此時,Javaeye上也湧現出了新的文章,Tin同學對新的方式做了詳細的表述:http://www.javaeye.com/topic/17939。

不過這個新的整合方式似乎並不被大家所看好。

lllyq 寫道

我覺得XWorkList, XWorkMap還是很有用的,挺好的設計,其實沒有必要deprecated。

moxie 寫道

集合支持不向下兼容。XWorkList已經是@deprecated,用它就錯,還不如直接刪除掉。在webwork2.2中,它需要為集合另外配置一個conversion.properties文件。真不明白,這樣有什麼優點?

這種新的整合方式,實際上只是解決了針對容器賦值,不需要依賴XWork的輔助類這樣的一個問題,不過其付出的代價,卻是多了一個配置文件,這也讓人非常郁悶。好好的類型轉化,平白無故多出了一個同package下的配置文件,這也無形中增加了編程的復雜度。

3. 現在,擁抱了泛型和Annotation的年代

實際上,在XWork發展到XWork-2.0.X之後,也開始注重了對泛型和Annotation的支持。所以,容器類型的轉化,我們也可以嘗試一下使用JDK的新特性來進行,當然這也是目前最為推薦的做法。

下面分別給出使用泛型和Annotation的代碼示例:

Html代碼

<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" />
   <input type="submit" value="submit" />
</form>

Java代碼

public class OgnlConversionAction extends ActionSupport {
   private static final long serialVersionUID = 4396125455881691845L;
   private static final Log logger = LogFactory.getLog(Policy.class);

   private List<User> users;

   @Element(value = User.class)
   private List users2;

   private Map<String, User> userMap;

   @Element(value = User.class)
   private Map userMap2;

   /* (non-Javadoc)
   * @see com.opensymphony.xwork2.ActionSupport#execute()
   */
   @Override
   public String execute() throws Exception {

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

     return super.execute();
   }

   // setters and getters
}

上面的代碼中,我們可以看到,如果你使用泛型,那麼你無需再使用任何額外的配置文件或者Annotation,XWork會把一切都為你准備好。如果你沒有使用泛型,那麼你可以使用Annotation來指定你需要進行轉化的對象類型。其中,對Map對象使用Annotation時,Element中的value所對應的值,是Map中的value所對應的class。

由此可見,泛型和Annotation,在一定程度上,還是可以簡化我們很多工作的。

文件上傳 

文件上傳其實也是參數傳遞的一種,所以從方案上來講,Struts2同樣使用了一個攔截器來處理。而這個攔截器,同樣來自於原來的Webwork,基本上沒有做什麼很大的改變。有關這個攔截器的詳細內容,我們也留一個懸念,在後續章節中詳細講解。目前,你只要知曉,這個攔截器能幫助你完成一切文件上傳相關的機制。

早在2005年,Quake Wang就對Webwork的文件上傳機制有了詳細的講解:http://www.javaeye.com/topic/10697

在這裡我簡單小結一下在進行文件上傳時的三大要點:

1. 在配置文件上傳時,攔截器的順序非常關鍵

Xml代碼

<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的特性,將這些文件和文件名等文件信息做封裝:

Java代碼

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;
   }
   /**
   * @param fileName
   *      The fileName to set.
   */
   public void setUploadFileName(String uploadFileName) {
     this.fileName = uploadFileName;
   }
}

在這個類中,我定義了upload表示上傳的文件,fileName表示上傳文件的文件名。請注意我整個文件中的最後一個方法:setUploadFileName。這個方法將保證FileUploadInterceptor在運行時,能夠正確設置上傳的文件名。

Java代碼

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

Java代碼

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

Java代碼

/**
* 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同學在2005年時的一篇文章:http://www.javaeye.com/topic/10507。文章針對的是Webwork2,但是無論是實現方式還是操作步驟,與Struts2是完全相同的。值得提醒的是,這篇文章的回復也非常有價值,在看文章的同時,不要忘記看回復。

不過針對Quake Wang的例子,我也想做一些補充。它的例子中,主要講述了Struts2中如何去做java.utils.Date的自動類型轉化,也正如後面回復中有人提到:

wolfsquare 寫道

如果我在界面上有兩種格式的日期怎麼辦?

例如一種短格式: SimpleDateFormat("yyyy-mm-dd"),一種長格式SimpleDateFormat("yyyy-mm-dd hh:MM:ss")

而Quake Wang對此是這樣解決的:

Quake Wang 寫道

可以根據你的應用情況,看哪種方式是比較常見的轉換規則,那麼把這個規則定成Application-wide conversion rules:

Java代碼

在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

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

Java代碼

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);
     }
     return result;
   }
   /**
   * Convert String to Date
   *
   * @param value
   * @return
   */
   private Date doConvertToDate(Object value) {
     Date result = null;
     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);
       }
     } 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;
   }
}

在我采用的方式中,依然采用Application-wide conversion rules,只是在自定義的Converter中,根據不同的日期形式進行逐個匹配,找到第一個匹配的日期類型進行轉化。當然,這是一種投機取巧的辦法,不過比較實用,適用於項目中不太使用國際化,日期的形式不太復雜的情況。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved