第七章 类型转换
7.1 概述
从页面输入的参数一般都是String类型,但相应的服务器获得参数时,需要的是各种不同的类型,因此,需要进行类型转换,struts提供了对类型转换的无缝支持。
有时类型转换发生错误时(如输入日期,用户输入abcd,这时类型转换就不会成功),此时,如果某个动作类没有实现了validateAware接口,此时struts在遇到类型转换错误时仍会继续调用其动作方法,当作错误没有发生;如果某个动作类实现了validateAware 接口,struts在遇到类型转换错误时将不会继续调用其动作方法:struts将检查相关动作元素里的声明是否包含着一个input结果,如果是,则将控制权交给那个result元素,如果找不到,则抛出异常。
如: <action name="myAction" class="myAction">
<result name="input">/jsp/Transaction.jsp</result>
<result name="success">/jsp/receipts.jsp</jsp>
</action>
此时myAction.java继承ActionSupport,间接地实现了ValidateAware接口,此时如果页面输入的参数不合法,则会转到Transactin.jsp页面。
7.2 类型转换错误消息的定制
一般的,如果输入的是错误格式信息,此时会用如下格式的提示信息Invalid field value for field fieldname,如输入日期错误,则提示Invalid field value for field "date"
该信息为默认的出错信息,可以修改,只要在相应的properties文件里写入invalid.fieldvalue.date=Please enter a date in MM/dd/yyyy,此时,如果输入错误格式日期,则会提示“please enter a date in MM/dd/yyyy”,但需要注意的是,className.properties必须和className.java在一个子目录中, className是包含date的类的名字。
除了可以定制出错信息,还可以定义出错信息的格式,每条出错信息都被打包在一个HTML span元素里,只要覆盖其中行标为.errorMessage的那个CSS样式来改变出错信息样式即可。如希望出错信息为红色字体显示,此时可以在该JSP页面里写上
<style>
.errorMessage{
color:red
}
</style>
7.3 类型转换器的定制
即意味着可以自己编写类型转换函数,自定义的类型转换器必须实现ognl.TypeConverter 接口或者对这个接口的实现(ognl.DefaultTypeConverter, org.apache.structs2.util.StrutsTypeConverter)进行扩展。
TypeConverter接口只有一个方法:java.lang.Object convertValue(java.util.Map context, java.lang.Object targer, java.lang.reflect.Member member, java.lang.String propertyName, java.lang.Object value,java.lang.Class toType);
其中:context:将在其中进行类型转换的OGNL上下文环境, 包含对于Value Stack栈和各种资源的引用
target:将在其中对有关属性进行设置的目标对象
member: 将被设置的类成员(构造器、方法或字段)的名字
propertyName:将被设置的属性的名字
value:将被转换的值
toType:转换结果的类型
获取valueStack栈:ValueStack stack=(ValueStack)context.get(ValueStack.VALUE_STACK);这样就可以用findValue方法去检索某个属性的值
获取ServletContex,HttpServletRequest, HttpServletResponse可通过
context.get(StructsStatics.SERVLET_CONTEXT);
context.get(StructsStatics.HTTP_REQUEST);
context.get(StructsStatics.HTTP_RESPONSE);
对ServletContext 里的参数可以在web.xml里面配置,如
<context-param>
<param-name>datePattern</param-name>
<param-value>yyyy-MM-dd</param-value>
</context-param>
为了让一个自定义的类型转换器发挥作用,还需要为所支持的每一种类型转换编写相应的代码。如:
public Object convertValue(Map context,Object target,Member member,String propertyName,Object value,Class toType){
if(toType==String.class){
// convert from double to Stirng and return the result;
}else if(toType==Double.class||toType==Double.TYPE){
//convert from string to double and return the result;
}
return null;
}
与自行实现的TypeConverter接口相比,对DefaultTypeConverter类进行扩展更容易,如:
import java.util.Map;
import java.lang.reflect.Member;
public class DefaultTypeConverter implements TypeConverter{
public Object convertValue(Map contet,Object target,Member member,String propertyName,Object value,Class toType){
return convertValue(context,value,toType);
}
public Object convertValue(Map context, Object value,Class toType){
return OgnlOps.convertValue(value,toType);
}
}
7.3.1 对自定义类型转换器的配置
两种方法,一种是基于字段,一种是基于类
对于基于字段的配置方法,可以为某个动作的各个属性分别指定一个自定义的转换器,具体的说,先要创建一个文件ActionClass-conversion.properties,其中ActionClass是相关动作类的名字,里面的内容为:fileld1=converter1 field2=converter2 ....请注意,这个配置文件必须和相关的动作类在一个子目录里,如,对于名为User类的date, name进行转换,date字段运用转换器converter1, name运用转换器converter2, 此时配置文件为User-conversion.properties, 内容为date=converter1 name=converter2
对于基于类的配置方法,即为每个类定义一个转换器,类里面的各个属性都用这个转换器,具体的说,在WEB-INF/classes目录下创建一个conversion.properties文件,内容为完整类名=converter....如设置User运用转换器converter,则内容为User=converter
7.4扩展StructsTypeConverter类
该抽象类有两个方法:convertFromString ,convertToString,使得能够有选择的转换,如下所示:
import java.lang.reflect.Member;
import java.util.Map;
import ognl.DefaultTypeConverter;
public abstract class StrutsTypeConverter extends DefaultTypeConverter{
/* (non-Javadoc)
* @see ognl.DefaultTypeConverter#convertValue(java.util.Map, java.lang.Object, java.lang.Class)
*/
@Override
public Object convertValue(Map context, Object value, Class toType) {
if(toType.equals(String.class)){
return convertToString(context,value);
}else if(value instanceof String[]){
return convertFromString(context,(String[])value,toType);
}else if(value instanceof String){
return convertFromString(context,new String[]{(String)value},toType);
}else{
return performFallbackConversion(context,value,toType);
}
}
public abstract Object convertToString(Map context, Object value);
public abstract Object convertFromString(Map context, String[] value, Class toType);
public abstract Object convertFromString(Map context, String value, Class toType);
protected Object performFallbackConversion(Map context, Object value, Class toType){
return super.convertValue(context, value, toType);
}
}
注意看代码里的红色部分。
例子:将一个color对象转换成string类型并完成反向转换的转换器
Color类
package app07c;
import com.opensymphony.xwork2.ActionSupport;
public class Color extends ActionSupport{
private int red;
private int green;
private int blue;
/**
* @return the red
*/
public int getRed() {
return red;
}
/**
* @param red the red to set
*/
public void setRed(int red) {
this.red = red;
}
/**
* @return the green
*/
public int getGreen() {
return green;
}
/**
* @param green the green to set
*/
public void setGreen(int green) {
this.green = green;
}
/**
* @return the blue
*/
public int getBlue() {
return blue;
}
/**
* @param blue the blue to set
*/
public void setBlue(int blue) {
this.blue = blue;
}
//返回给定基色的十六进制成分值
//Integer.toHexString(int i),将i转换成十六进制
public String getHexCode(){
return (red<16?"0":"")+Integer.toHexString(red)+(green<16?"0":"")+Integer.toHexString(green)+(blue<16?"0":"")+Integer.toHexString(blue);
}
}
Design类
package appl7c;
import com.opensymphony.xwork2.ActionSupprot;
public class Design extends ActionSupport{
private String designName;
private Color color;
//getter and setter
}
MycolorConverter类
package app07c;
import java.util.Map;
import org.apache.struts2.util.StrutsTypeConverter;
import com.opensymphony.xwork2.util.TypeConversionException;
public class MyColorConverter extends StrutsTypeConverter{
@Override
public Object convertFromString(Map context, String[] values, Class toClass) {
boolean ok=false;
String rgb=values[0];
//String.split(String regex):该函数是用给定的正则表达式拆分该字符串,如3,20,124(","),则会返回{"3","20","124"}这个字符串数组
String[] colorComponents=rgb.split(",");
if(colorComponents!=null&&colorComponents.length==3){
String red=colorComponents[0];
String green=colorComponents[1];
String blue=colorComponents[2];
int redCode=0;
int greenCode=0;
int blueCode=0;
try{
redCode=Integer.parseInt(red.trim());
greenCode=Integer.parseInt(green.trim());
blueCode=Integer.parseInt(blue.trim());
if(redCode>=0&&redCode<256&&greenCode>=0&&greenCode<256&&blueCode>=0&&blueCode<256){
Color color=new Color();
color.setRed(redCode);
color.setGreen(greenCode);
color.setBlue(blueCode);
ok=true;
return color;
}
}catch(NumberFormatException e){
}
if(!ok){
throw new TypeConversionException("Invalid color codes");
}
}
return null;
}
@Override
public String convertToString(Map context, Object o) {
Color color=(Color)o;
return color.getRed()+","+color.getGreen()+","+color.getBlue();
}
}
配置文件:将Color类映射到MyColorConverter
xwork-conversion.properties
app07c.Color=app07c.converter.MyColorConverter
7.5与复杂对象的使用
即将表单字段映射到多个对象的不同属性,使用OGNL可以做到
如:一个表单对应一个Admin的action, Admin有两个属性,一个ID,一个Employee类,Employee类有一个name和birthDate属性,将表单的字段映射到id,employee如下:
<s:form action="Admin">
<s:textfield name="ID" />
<s:textfield name="employee.name" />
<s:textfield name="employee.birthDate" />
</s:form>
如果为birthDate指定转换器,则在Admin-conversion.properties里注册即可
employee.birthDate=app07d.converter.MyDateConverter
7.6 与collection配合使用
即表单里可以一次添加多条信息(如添加员工,要输入员工firstname,lastname,birthDate),此时表单与action相对应时,action里的属性employee为collection类型
如例子所示:
Admin类
import java.util.Collection;
import com.opensymphony.xwork2.ActionSupport;
public class Admin extends ActionSupport{
private Collection<Employee> employees;
public Collection<Employee> getEmployees() {
return employees;
}
public void setEmployees(Collection<Employee> employees) {
this.employees = employees;
}
}
Employee类里有属性firstname,lastname,birthDate
public class Employee {
private String firstname;
private String lastname;
private String birthDate;
//getter and setter
}
Admin.jsp
<table>
<tr>
<td><s:textfield name="employees[0].firstname" /></td>
<td><s:textfield name="employees[0].lastname" /></td>
<td><s:textfield name="employees[0].birthDate" /></td>
</tr>
<tr>
<td><s:textfield name="employees[1].firstname" /></td>
<td><s:textfield name="employees[1].lastname" /></td>
<td><s:textfield name="employees[1].birthDate" /></td>
</tr>
</table>
提交表单时,employees[0]将为employees里的第一个元素
输出表单时,可以直接用iterator,里面的value属性为employees即可
注:上面的例子是硬性的每次只能输入两名员工的信息,动态的一次输入多个员工信息的实现如下:
<s:iterator value="new int[#parameters.count[0]]" status="stat">
<tr>
<td><s:textfield name="%{'employees['+stat.index+'].firstname'}" /></td>
<td><s:textfield name="%{'employees['+stat.index+'].lastname'}" />
</td>
<td><s:textfield
name="%{'employees['+stat.index+'].birthDate'}" /></td>
</tr>
</s:iterator>
注:如果要静态规定输入员工的信息数,可以在iterator的value属性为new int[4],如果想使用用户传过来的参数,则如上所示,但上面的[0]不可少,因为parameters总是返回一个string数组,而不是一个String,
请注意嵌在末尾的count请求参数http://localhost:8080/Admin.action?count=n
7.7 与MAP配合使用
这一节没什么特别,唯一一点:如employees为MAP类型,要取得其分键值的集合可用如下:
<s:iterator value="employees.keySet()" var="key" status="stat">
<ul>
<li><s:property value="#key" />:
<s:property value="employees[#key].firstname" />
<s:property value="employees[#key].lastname" />
</li>
</ul>
</s:iterator>