UML软件工程组织

怎样真正的在Struts框架下使用时间类型
来源:http://www.chinaaspx.com
使用时间类型,这谁不会,不就是java.util下的几个类吗,再加上java.sql和java.text下的几个类,这会有什么问题吗?Struts要是连时间都处理不了,那还能干嘛?

在实际应用中,我就发现Struts确实连有些简单的时间都处理不了(不知是我使用的方法不对还是Struts确实没有考虑到)。顺便你也能了解Struts是怎么把form里的请求参数populate到ActionForm里面的。

今天下午同事告诉我把有java.util.Date类型属性的类存入数据库时出错,把这个属性删除就没有问题了。当时我就想到是RequestProcessor在processPopulate()时出错了,因此在它的这个方法设了断点并跟踪了进去。

当然,它最先要调用ActionForm的reset()方法,然后调用实际处理populate(将请求参数传给ActionForm)的RequestUtils.populate()方法。RequestUtils的这个静态方法最先是处理Multipart的(即文件上传等多部分)的方法,然后将所有的请求都放在叫properties的HashMap里并循环处理它:

names = request.getParameterNames();
while (names.hasMoreElements())
{
String name = (String) names.nextElement();
String stripped = name;
if (prefix != null)
{
if (!stripped.startsWith(prefix))
{
continue; 
}
stripped = stripped.substring
(prefix.length());
}
if (suffix != null)
{
if (!stripped.endsWith(suffix))
{
continue;
}
stripped = stripped.substring
(0, stripped.length() 
- suffix.length());
}
if (isMultipart)
{
properties.put(stripped, 
multipartParameters.get(name));
}
else 
{
properties.put
(stripped, request.getParameterValues(name));
} 
}

实际处理它们的是下面的:BeanUtils.populate(bean, properties);其中bean就是接受数据的ActionForm,而properties里面则是所有的请求的键-值对(键和值都是字符串,http协议的特点)。

再看看BeanUtils的静态(类)方法populate是怎么处理的:


// Loop through the property 
name/value pairs to be set
Iterator names =
properties.keySet().iterator(); 
while (names.hasNext())
{
// Identify the property name
and value(s) to be assigned
String name = (String) names.next(); 
if (name == null)
{ 
continue;
}
Object value = properties.get(name);
// Perform the assignment for this property
setProperty(bean, name, value);
}



它是循环所有的请求参数,把实际的工作又交给了setProperty方法。这个类就是:一上来20多行都在一个if (log.isTraceEnabled()){}里面。建议在写Action 的execute()或被execute()调用的业务方法中使用Commons Logging 来代替System.out.println()——当要你把成百上千的System.out.println()去掉的时候你就会觉得Commons Logging有多好用了。它的用法是:


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; 
private/protected static Log log = 
LogFactory.getLog(DispatchAction.class);



如果你用的是DispatchAction,那你就不要自己定义Log的实例了,因为它已经有一个protected的Log实例,直接使用即可。使用方法是:


if (log.isInfoEnabled())
{
log.Info("some information.");
}



Logging把消息分为6种级别,debug,error,fatal,info,trace,warn。比如,你想记录一条消息,它只是为了给用户一个警告,则可以使用warn。为什么在每个log.Info()前做一次判断呢?难道如果log级别不允许Info,log.Info()仍然能Info吗?当然不是。它的作用是提高效率。

比如有个消息是计算前一万个自然数的和(这种消息可能少见)。用直接log.Info()


int sum=0;
for(int i=0;i<10000;i++)
{
sum+=i; 
}
log.Info("the sum of form 1 to 10000 is : "_sum);



如果log.Info是不允许的,那求10000个数的和就白求的。当然如果你的计算机很快或和高斯一样聪明,直接log.Info()也每什么问题。

闲话少说,回到180多行的BeanUtils.setProperty()方法。这个方法先是处理nested属性,也就是xxx.xxx的请求参数。我们只看看处理简单属性的必须过程。下面这端代码有点长,但它只做了一件事:将字符串的请求参数转成ActionForm的类型。

比如:你在ActionForm里有个Integer userAge;然后HTTP请求参数里可能会有http://localhost:8080/xxx.do?userAge=21。传入的是字符串,目标是专程Integer。首先它当然会根据userAge这个字符串查找相应的ActionForm,如果这个ActionForm有个属性也叫userAge,然后就会把这个userAge的类型存到type里,type的定义是:


Class type = null;


得到type的代码很长,这是因为要它考虑很多情况,例如DynaActionForm。


// Convert the specified value to
the required type
Object newValue = null; 
if (type.isArray() && (index < 0))
{
// Scalar value into array
if (value == null)
{
String values[] = new String[1];
values[0] = (String) value;
newValue = ConvertUtils.convert
((String[]) values, type);
}
else if (value instanceof String)
{ 
String values[] = new String[1];
values[0] = (String) value;
newValue = ConvertUtils.convert
((String[]) values, type);
}
else if (value instanceof String[]) 
{
newValue = ConvertUtils.convert
((String[]) value, type);
}
else
{ 
newValue = value;
}
  }
  else if (type.isArray()) 
  {
  // Indexed value into array
  if (value instanceof String) 
  {
  newValue = ConvertUtils.convert((String) 
  value, type.getComponentType());
  } 
  else if (value instanceof String[])
  {
  newValue = ConvertUtils.convert
  (((String[]) value)[0], 
  type.getComponentType()); 
  } 
  else 
  {  
  newValue = value;  
  }  
  }
  else
  {
  // Value into scalar 
  if ((value instanceof String)
  || (value == null))
  { 
  newValue = ConvertUtils.convert
  ((String) value, type);
  }
  else if (value instanceof String[])
  { 
  newValue = ConvertUtils.convert
  (((String[]) value)[0], type);
  }
  else if 
  (ConvertUtils.lookup(value.getClass())
  != null)
  {                
  newValue =
  ConvertUtils.convert(value.toString(), type);
  // Here is my program's break point 
  }
  else 
  { 
  newValue = value;
  } 
  }



最后是:调用PropertyUtils的一些方法设置值。下面代码的第一种情况是有索引的,即你在请求参数里传了field[0]=123之类的参数,第二种是Map类型的,传的是map(key)=value之类的参数,最一般的就是调用第三个方法:


if (index >= 0) 
{ 
PropertyUtils.setIndexedProperty
(target, propName, index, newValue); 
} else if (key != null)     
{
PropertyUtils.setMappedProperty
(target, propName, key, newValue); 
}
else
{ 
PropertyUtils.setProperty
(target, propName, newValue); 
}



当然还可以在跟踪下去,不过和这个主题没什么关系了。大概的流程是:setProperty()方法再调用setNestedProperty()方法(还是代理),在调用setSimpleProperty(),最后通过java.lang.reflect包调用你在ActionForm里写的setXXX()方法,如setUserAge(Integer userAge)等。

现在说说为什么不能populate java.util.Date类型的数据。关键是ConvertUtils.convert(),即上文有注释的地方。如果这个方法返回的是一个java.util.Date类型的对象,当然后面都不会有问题。但我发现实际运行的结果是,newValue还是String类型的,因此在后面通过reflection调用setXXX时出错。

你或许会奇怪ConvertUtils包竟然连java.util.Date都不支持,我也觉得不可思异。我还以为是我使用的不对,然后进入这个类一看,确实是不支持。

另外,会不会即时是字符串的,org.apache.commons.beanutils.PropertyUtils.setProperty()也有能力处理呢?于是又写了个小程序测试:


public class SetSimplePropertyTest
{ 
public SetSimplePropertyTest()
{
}  
public static void main(String[] args)
{ 
SetSimplePropertyTest
setSimplePropertyTest1 = 
new SetSimplePropertyTest();  
String dateStr="2004-01-01 19:00:00"; 
test.DataBean dataBean=new DataBean();
try 
{ 
org.apache.commons.beanutils.
PropertyUtils.setProperty(dataBean,
"receiveTime", dateStr);
}
catch (Exception e)
{
e.printStackTrace(); 
}
System.out.println
(dataBean.getReceiveTime().toString());
}



运行是抛出异常,证明处理不了。

问题找到了,那该怎么解决呢?当然最简单的方法就是使用ConvertUtils能转的java.sql.DateTime等,比较复杂一点的方法就是自己写一个ConvertUtils。当然,如果你把日前存成String,那更没问题,但如果要将它存入数据库,还得转。

尤其在使用DAO模式时,我们可能用BeanUtils.CopyProperties()方法实现将一个ActionForm拷贝到一个DTO(or VO)对象中时会很麻烦。

还有一个比较好的方法是,属性定义成java.util.Date,但为Struts提高另一个getter/setter方法。这种方法是在middlegen自动生成的JSP页面看到的。例如:


private java.util.Date saveDate;
//普通的set/get方法
public void setSaveDate
(java.util.Date saveDate)
{
this.saveDate=saveDate;
} 
public java.util.Date getSaveDate()
{
return this.saveDate;
}
//为Struts准备的方法,
时期的格式假定是 2000-12-31 23:59:59 
public void setSaveDateAsString
(String saveDate)
{
java.text.DateFormat dateFormat 
=new java.text.SimpleDateFormat
("yyyy-MM-dd HH:mm:ss");                 
this.saveDate=dateFormat.parse(saveDate); 
}
public String getSaveDateAsString()
{ 
java.text.DateFormat dateFormat =new 
java.text.SimpleDateFormat
("yyyy-MM-dd HH:mm:ss");
return dateFormat.Format(this.saveDate);  
}



然后在JSP中使用:


<html:form action="xxx.do">
<html:text property="saveDateAsString"/>
</html:form>
 

版权所有:UML软件工程组织