3.4 自定义类型转换器
Struts 2已经实现了一些常用的类型转换器,但是这些类型转换器毕竟还是有限的。如果是开发者自己定义的数据类型,就必须使用自定义类型转换器来进行转换。
3.4.1 项目需求
前面介绍的都是Sturts 2内建的类型转换器,下面来看如何创建自定义的类型转换器。
加入现在项目要求有如下一个输入页面,如图3.23所示。在该输入页中的“name”文本框中输入“firstName lasNamet”模式的字符串,注意中间使用空格隔开。单击“input”按钮提交,将该字符串转换成一个Name对象。
图3.23 用户输入页
可以在Action中接受该字符串,并使用空格分隔该字符串。如何将两个字符串分别设置到Name对象的属性中。但是这样就成了手动执行的类型转换了。Struts 2可以通过设计一个自定义的类型转换器来自动完成类型转换。
(1)新建一个用户输入页,添加3个文本框。name属性分别为name、age、birth。然后添加一个提交按钮,代码如下所示。
<%@ page language="java" import="java.util.*" pageEncoding="gb2312"%> <%@ taglib prefix="s" uri="/struts-tags" %> <! DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>input</title> </head> <body> <! -- 输入表单 --> <s:form action="input"> <! -- 单行文本框,用来输入name值 --> <s:textfield name="name" label="name"></s:textfield> <! -- 单行文本框,用来输入age值 --> <s:textfield name="age" label="age"></s:textfield> <! -- 单行文本框,用来输入birth值 --> <s:textfield name="birth" label="birth"></s:textfield> <s:submit value="input"></s:submit> </s:form> </body>
(2)新建Name类。该类是一个简单的javaBean,其属性分别为“firstName”和“lastName”,添加setter和getter方法,代码如下所示。
package net.hncu.bean; public class Name { // Name类包含两个字符串类型的属性 private String firstName; private String lastName; //Name类的firstName属性和lastName属性的setter和getter方法 public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } }
(3)新建业务逻辑组件Action,根据提交的参数分别添加3个属性。其中“name”属性为自定的Name类型,“age”属性为int类型,“birth”属性为Date(日期)类型。分别添加属性的setter和getter方法,代码如下所示。
package net.hncu.action; import java.util.Date; import net.hncu.bean.Name; import com.opensymphony.xwork2.ActionSupport; public class InputAction extends ActionSupport { // name属性类型为自定义的Name类型 private Name name; // age属性类型为int private int age; // birth属性类型为Date private Date birth; // name属性的setter和getter方法 public Name getName() { return name; } public void setName(Name name) { this.name = name; } // age属性的setter和getter方法 public int getAge() { return age; } public void setAge(int age) { this.age = age; } // birth属性的setter和getter方法 public Date getBirth() { return birth; } public void setBirth(Date birth) { this.birth = birth; } // execute方法 public String execute() throws Exception { return SUCCESS; } }
(4)在“struts.xml”文件中配置LoginAction,并定义处理结果与视图资源之间的关系。如果LoginAction处理后返回“success”,则跳转到“output.jsp”页面,如果类型转换失败,系统会自动跳转到“input”对应的视图资源—“input.jsp”页面,代码如下所示。
<struts> <package name="struts2" extends="struts-default"> <! -- 定义input的Action,其实现类为net.hncu.action.InputAction--> <action name="input" class="net.hncu.action.InputAction"> <! -- 定义处理结果与视图资源之间的关系--> <result name="success">/output.jsp</result> <result name="input">/input.jsp</result> </action> </package> </struts>
从上代码可以看出,用户提交的name参数值是一个字符串,但是在InputAction中name属性接受的却是Name类型的对象。因此必须创建一个自定义的转换器来负责将这个字符串类型参数转换成为Name类型的实例。
3.4.2 实现自定义类型转换器
前面提到过,Struts 2的类型转换器是基于OGNL实现的。首先在导入的OGNL类库中找到TypeConverter类,代码如下所示。
package ognl; import java.lang.reflect.Member; import java.util.Map; public interface TypeConverter { public Object convertValue(Map context, Object target, Member member, String propertyName, Object value, Class toType); }
TypeConvert是一个接口,要实现类型转换器就必须实现该接口并实现该接口中的所有方法。OGNL中同样提供了一个实现了该接口的类DefaultTypeConverter,通过继承该类可以简化代码。
package ognl; import java.lang.reflect.Member; import java.util.Map; public class DefaultTypeConverter implements TypeConverter { public DefaultTypeConverter() { super(); } public Object convertValue(Map context, Object value, Class toType) { return OgnlOps.convertValue(value, toType); } public Object convertValue(Map context, Object target, Member member, String propertyName, Object value, Class toType) { return convertValue(context, value, toType); } }
要实现自定义的转换器,只要通过继承DefaultTypeConverter即可。该类提供了两个重载的方法,为了操作更加简单,可以使用第一个。在自定义类型转换器时,就可以通过复写该方法以实现类型转换。下面来介绍一个Name类型转换器,代码如下所示。
package net.hncu.convert; import java.util.Map; import net.hncu.bean.Name; import ognl.DefaultTypeConverter; public class NameConvert extends DefaultTypeConverter { public Object convertValue(Map context, Object value, Class toType) { //toType表示要转换到的类型是什么 //首先判断如果要转换的类型是Name if(Name.class == toType) { //value中包含了用户请求的参数信息,因为可能一个参数有多个值,所以其类型为String数组 String[] strs = (String[])value; //处理请求参数数组中的第一个数组元素,通过空格把这个值分成多个字符串并保存在字符串数组中 String[] paramValues = strs[0].split(" "); //新建一个Name实例 Name nb = new Name(); //为Name实例赋值 nb.setFirstName(paramValues[0]); nb.setLastName(paramValues[1]); //返回转换后的Name实例 return nb; } //如果要转换的类型为String if(String.class == toType) { //通过强制转换把value转换成Name实例 Name nb = (Name)value; //取出Name实例中的属性值 String firstName = nb.getFirstName(); String lastName = nb.getLastName(); //实例化一个StringBuilder用来构造字符串 StringBuilder sb = new StringBuilder(); //构造字符串 sb.append("firstName is ").append(firstName).append(", lastName is ").append(lastName); //返回StringBuilder的字符串形式 return sb.toString(); } return null; } }
下面来介绍一下convertValue方法中的3个参数。
❑ 第一个参数:context,是表示类型转换的上下文。
❑ 第二个参数:value,表示需要转换的参数。从字符串转换为Name类型时,value表示的是用户提交的字符串数组(之所以是字符串数组,是因为如果多个表单元素的name属性相同时,那么提交的参数就不再是字符串了,而是字符串数组)。如果是从Name类型转换为String类型时,value表示的是一个Name类型的实例。
❑ 第三个参数:toType,表示转换后的目的类型。如果是字符串转换为Name类型,那么目的类型就是Name类型,如果从Name类型转换为String类型,那么目的类型就是String类型。
程序首先判断toType的类型是什么,如果是Name类型,就表示是从字符串转换为Name类型。那么就可以通过第二个参数value得到一个字符串数组,通过取出数组中的第一个字符串并使用空格分割。系统将分割后的字符串分别设置到Name实例的属性中。
如果toType的类型为字符串类型。那么value就是一个Name类型的实例了,只要通过强制转换就可以了。然后分别取出Name实例中的属性值并构造一个字符串就可以了。
仅仅有这个自定义的类型转换器还不行,系统根本就不知道什么时候调用它,如何调用它,所以还必须要注册类型转换器。
3.4.3 注册自定义类型转换器
要想使自定义的类型转换器起作用,还必须将其在Web应用中注册。类型转换器的注册分为两种,一种是局部类型转换器,一种是全局类型转换器。
局部类型转换器和全局类型转换器从字面上来看,就是一个可以进行全局的类型转换,一个只能进行局部的类型转换。
注册局部类型转换器非常简单,只需要在相应的Action目录下新建一个资源文件。该资源文件名格式如下。
ActionName-conversion.properties
其中ActionName表示需要进行转换的Action的类名,“-conversion.properties”字符串则是固定格式的。该文件也是一个典型资源文件,文件由键值对组成,如下所示。
name=net.hncu.convert.NameConvert
其中name表示要进行转换的属性,net.hncu.convert.NameConvert表示进行转换的自定义类型转换器。
下面来看Struts 2是如何通过这个配置文件来实现类型转换的。首先当InputAction执行到setName(Name name)时,系统会找到是否注册了类型转换器,如果找到了相应的资源文件,就会将接收到的参数信息(String类型)转交给NameCovert进行处理,然后再将返回的Name对象实例设置到InputAction的name属性中。
3.4.4 新建显示页测试程序
自定义类型转换器也注册好了,现在来创建一个显示页类进行输出。测试程序是否能完成类型转换,步骤如下所示。
(1)新建显示页,在该页面中通过<s:property>标签输出指定的属性值,代码如下所示。
<%@ page language="java" import="java.util.*" pageEncoding="gb2312"%> <%@ taglib prefix="s" uri="/struts-tags" %> <! DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>output</title> </head> <body> <! -- 输出name属性的值 --> <s:property value="name"/><br> <! -- 输出age属性的值 --> age:<s:property value="age"/><br> <! -- 输出birth属性的值 --> birth:<s:property value="birth"/> </body> </html>
(2)在输入页中输入用户信息。如在“name”文本框中输入“bu da”,在“age”文本框中输入“20”,在“birth”文本框中输入“1988-04-25”,如图3.24所示。
(3)单击“input”按钮提交用户信息。页面提交到显示页,在显示页中显示输入的数据,如图3.25所示。
图3.24 输入用户信息
图3.25 显示用户信息
在使用<s:property>标签输出name属性值时,系统会使用类型转换器将Name类型的实例转换成String类型数据。
3.4.5 程序执行流程
项目成功地完成了预定的功能。为了加深印象,下面讲一下程序执行的流程。程序执行中有两个重要的步骤。
❑ 从字符串转换为自定义类型,其执行流程图如图3.26所示。
❑ 从自定义类型转换为字符串类型,其执行流程图如图3.27所示。
图3.26 字符串转换为自定义类型执行流程
图3.27 自定义类型转换为字符串类型执行流程
3.4.6 使用Struts 2提供的StrutsTypeConverter
前面介绍了TypeConverter接口以及实现TypeConverter接口的DefaultTypeConverter类。但是大家有没有发现通过继承DefaultTypeConverter类来实现自定义类型转换器比较麻烦?
其实Struts 2提供了一个StrutsTypeConverter的抽象类,开发时可以直接继承这个类来进行转换器的构建。通过继承该类来构建类型转换器,可以不用对转换的类型进行判断。这样又可以省去不少的时间和代码。
StrutsTypeConverter类在导入的“struts2-core-2.0.11.jar”库文件中。和关联OGNL源代码类型一样,可以把class文件和源代码进行关联。在单击“External Folder”按钮后选择Struts 2的解压目录,这里分别选择“src”、“core”、“src”、“main”、“java”目录,单击“OK”按钮完成关联。StrutsTypeConverter. class代码如下所示。
package org.apache.struts2.util;
import java.util.Map; import ognl.DefaultTypeConverter; public abstract class StrutsTypeConverter extends DefaultTypeConverter { public Object convertValue(Map context, Object o, Class toClass) { if (toClass.equals(String.class)) { return convertToString(context, o); } else if (o instanceof String[]) { return convertFromString(context, (String[]) o, toClass); } else if (o instanceof String) { return convertFromString(context, new String[]{(String) o}, toClass); } else { return performFallbackConversion(context, o, toClass); } } protected Object performFallbackConversion(Map context, Object o, Class toClass) { return super.convertValue(context, o, toClass); } public abstract Object convertFromString(Map context, String[] values, Class toClass); public abstract String convertToString(Map context, Object o); }
现在来分析StrutsTypeConverter的源代码。首先看这个类的定义,如下代码所示。
public abstract class StrutsTypeConverter extends DefaultTypeConverter
以上代码表示这个类是这抽象类,并继承了OGNL中的DefaultTypeConverter这个类。接下来看该类的convertValue方法,如下所示。
public Object convertValue(Map context, Object o, Class toClass) { if (toClass.equals(String.class)) { return convertToString(context, o); } else if (o instanceof String[]) { return convertFromString(context, (String[]) o, toClass); } else if (o instanceof String) { return convertFromString(context, new String[]{(String) o}, toClass); } else { return performFallbackConversion(context, o, toClass); } }
StrutsTypeConverter中重写了DefaultTypeConverter的convertValue方法,并对要转换的类型进行了判断,同时提供了convertFromString(从String类型转换到其他对象类型)和convertToString(从其他对象类型转换为String类型)这两个抽象方法供开发者进行重写。这样开发者在构造转换器时只需继承StrutsTypeConverter这个类,重写convertFromString和convertToString这两个方法即可进行转换类型的判断。
StrutsTypeConverter类已经实现了对转换类型的判断,而这里所要做的仅仅是重写convert-FromString和convertToString这两个方法。改进上个实例的NameConvert,代码如下所示。
package net.hncu.convert; import java.util.Map; import net.hncu.bean.Name; import org.apache.struts2.util.StrutsTypeConverter; public class NameConvert extends StrutsTypeConverter { public Object convertFromString(Map context, String[] values, Class toClass) { //处理请求参数数组中的第一个数组元素,通过空格把这个值分成多个字符串并保存在字符串数组中 String[] paramValues = values[0].split(" "); //新建一个Name实例 Name nb = new Name(); //为Name实例赋值 nb.setFirstName(paramValues[0]); nb.setLastName(paramValues[1]); //返回转换后的Name实例 return nb; } public String convertToString(Map context, Object o) { //通过强制转换把value转换成Name实例。 Name nb = (Name)o; //取出Name实例中的属性值 String firstName = nb.getFirstName(); String lastName = nb.getLastName(); //实例化一个StringBuilder用来构造字符串 StringBuilder sb = new StringBuilder(); //构造字符串 sb.append("firstName is ").append(firstName).append(", lastName is ").append(lastName); //返回StringBuilder的字符串形式 return sb.toString(); } }
对比这两个转换器,可以发现代码没有很大的改变。最大的改变就是通过继承StrutsTypeConverter后不必对转换到的类型进行判断,这部分工作交给StrutsTypeConverter自行处理。开发者所关心的只有实现从String转换到其他对象类型和从其他对象类型转换为String类型这两部分代码。