大部分时候,我们使用Struts 2提供的类型转换器,以及基于OGNL的类型转换机制,就能满足大部分类型转换需求。但在有些特殊的情形下,例如需要把一个字符串转换成一个复合对象(例如 User对象)时,这就需要使用自定义类型转换器。例如,用户输入一个abc,xyz字符串,我们需要将其转换成一个User类型实例,其中abc作为User实例的name属性值,而xyz作为User实例的pass属性值。
假设本系统有一个如图4.2所示的表单输入页面。
图4.2 输入字符串的页面
如图4.2所示的页面包含一个名为user的表单域,这将产生一个名为user的请求参数,该请求对应的Action类代码如下。
程序清单:codes\04\4.1\LocalConverter\WEB-INF\src\org\crazyit\app\action\LoginAction.java
public class LoginAction implements Action
{
private User user;
private String tip;
//user属性的setter和getter方法
public void setUser(User user)
{
this.user = user;
}
public User getUser()
{
return this.user;
}
//省略tip属性的setter和getter方法
...
public String execute() throws Exception
{
if (getUser().getName().equals("crazyit.org")
&& getUser().getPass().equals("leegang") )
{
setTip("登录成功!");
return SUCCESS;
}
else
{
setTip("登录失败!!");
return ERROR;
}
}
}
从上面的代码中可以看出,该Action的user属性是User类型,而对应表单页发送的user请求参数则只能是字符串类型。Struts 2功能虽然强大,但它依然不知道如何完成字符串和User对象之间的转换,这种转换需要程序员来完成。
User类就是一个普通的JavaBean类,关于此类代码读者请参考光盘中的程序清单:codes\04\4.1\LocalConverter\WEB-INF\src\org\crazyit\app\domain\User.java。
Struts 2的类型转换器实际上依然是基于OGNL框架的,在OGNL项目中有一个TypeConverter接口,这个接口就是自定义类型转换器必须实现的接口。该接口的定义代码如下:
//OGNL提供的类型转换器接口
public interface TypeConverter
{
public Object convertValue(Map context, Object target,
Member member, String propertyName, Object value, Class toType);
}
实现类型转换器必须实现上面的TypeConverter,不过上面接口里的方法太过复杂,所以OGNL项目还为该接口提供了一个实现类:DefaultTypeConverter,通常都采用扩展该类来实现自定义类型转换器。实现自定义类型转换器需要重写DefaultTypeConverter类的convertValue方法。
下面是本应用所使用的类型转换器的代码。
程序清单:codes\04\4.1\LocalConverter\WEB-INF\src\org\crazyit\app\converter\UserConverter.java
public class UserConverter extends DefaultTypeConverter
{
//类型转换器必须重写convertValue方法,该方法需要完成双向转换
public Object convertValue(Map context
, Object value, Class toType)
{
//当需要将字符串向User类型转换时
if (toType == User.class )
{
//系统的请求参数是一个字符串数组
String[] params = (String[])value;
//创建一个User实例
User user = new User();
//只处理请求参数数组第一个数组元素,
//并将该字符串以英文逗号分割成两个字符串
String[] userValues = params[0].split(",");
//为User实例赋值
user.setName(userValues[0]);
user.setPass(userValues[1]);
//返回转换来的User实例
return user;
}
else if (toType == String.class )
{
//将需要转换的值强制类型转换为User实例
User user = (User) value;
return "<" + user.getName() + ","
+ user.getPass() + ">";
}
return null;
}
}
上面的程序的粗体字代码是实现类型转换的关键,第一段粗体字代码实现将字符串转换成User对象,第二段粗体字代码实现将User对象转换成字符串。读者可能对上面实现的类型转换器感到有一些迷惑,下面是关于上面的类型转换器的几点说明。
1.convertValue方法的作用
为了让该方法实现双向转换,我们通过判断toType的类型即可判断转换的方向。toType类型是需要转换的目标类型,当toType类型是User类型时,表明需要将字符串转换成User实例;当toType类型是String类型时,表明需要把User实例转换成字符串类型。图4.3显示了这种toType参数和转换方向之间的关系。
方向之间的关系
一旦通过toType类型判断了类型转换的方向后,我们就可以分别实现两个方向的转换逻辑了。
2.convertValue方法参数和返回值的意义
通过上面的介绍可以看出,实现类型转换器的关键就是实现convertValue方法,该方法有如下三个参数。
Ø 第一个参数:context是类型转换环境的上下文。
Ø 第二个参数:value是需要转换的参数。随着转换方向的不同,value参数的值也是不一样的,当把字符串类型向User类型转换时,value是原始字符串数组;当需要把User类型向字符串类型转换时,value是User实例。
Ø 第三个参数:toType是转换后的目标类型,这个参数前面已经介绍了。
该方法的返回值就是类型转换后的值,该值的类型也会随转换方向的不同而不同,当把字符串向User类型转换时,返回值类型就是User类型;当需要把User类型向字符串类型转换时,返回值类型就是字符串类型。
由此可见,转换器的convertValue方法,接收需要转换的值,需要转换的目标类型为参数,然后返回转换后的目标值。
3.当把字符串向User类型转换时,为什么value是一个字符串数组,而不是一个字符串
很多读者会感到疑惑:当我们需要把字符串转换成User类型时,为什么value的值是字符串数组,而不是一个字符串。因为在前面的介绍中,我们总是说浏览器发送的请求参数类型是字符串,而不是字符串数组。
在如图4.4所示的页面中,姓名输入框的值只能是一个普通字符串。但选择课程的列表框的值则可以同时选择多个值。因此,浏览者向服务器发送请求时,该下拉列表框对应的请求参数则是字符串数组。
图4.4 包含字符串数组请求参数的表单页
对于DefaultTypeConverter转换器而言,它必须考虑到最通用的情形,因此它把所有的请求参数都视为字符串数组,而不是字符串。对字符串请求参数而言(例如姓名请求参数),转换器把该请求参数值当成长度为1的数组。
提示:
可以认为DefaultTypeConverter是通过HttpServletRequest的getParameter Values(name)方法来获取请求参数值的。因此它获取的请求参数总是字符串数组,如果请求参数只包含一个单个的值,则该请求参数的值是一个长度为1的字符串数组。