Struts2中的参数传递

我们知道,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代码

          男    女     

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

                          

Java代码

public class OgnlConversionAction extends ActionSupport {   private static final long serialVersionUID = 4396125455881691845L;   private static final Log logger = LogFactory.getLog(Policy.class);   private List users;   @Element(value = User.class)   private List users2;   private Map 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代码

     

具体来说,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 root and context*     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中,根据不同的日期形式进行逐个匹配,找到第一个匹配的日期类型进行转化。当然,这是一种投机取巧的办法,不过比较实用,适用于项目中不太使用国际化,日期的形式不太复杂的情况。

期待遇上一位撑着油纸伞,结着忧愁丁香一样的姑娘;或者在春暖花开时,

Struts2中的参数传递

相关文章:

你感兴趣的文章:

标签云: