所有的MVC框架,都需要负责解析HTTP请求参数,并将请求参数传给控制器组件,HTTP请求参数都是字符串类型,但Java是强类型的语言,因此MVC框架必须将这些字符串参数转换成相应的数据类型—这个工作时所有的MVC框架都应该提供的功能。
Struts2的类型转换可以基于OGNL表达式,只要把HTTP参数(表单元素和其他GET/POST的参数)命名为合法的OGNL表达式,就可以充分利用struts2的类型转换机制。
1.struts2内建的类型转换器
- boolean和Boolean:完成字符串和布尔值之间的转换。
- char和Character:完成字符串和字符之间的转换。
- int和Integer:完成字符串和整型值之间的转换。
- long和Long:完成字符串和长整型值之间的转换。
- float和Float:完成字符串和单精度浮点值之间的转换。
- double和Double:完成字符串和双精度浮点值之间的转换。
- Date:完成字符串和日期类型之间的转换,日期格式使用用户请求所在Locale的SHORT格式。
- 数组:在默认情况下,数组元素是字符串,如果用户提供了自定义类型转换器,也可以是其他复合类型的数组。
- 集合:在默认情况下,假定集合元素类型时String,并创建一个新的ArrayList封装所有的字符串。
2.基于OGNL的类型转换
借助OGNL表达式的支持,struts2允许以另一种简单方式将请求参数转换成复合类型。
LoginAction.java
public class LoginAction extends ActionSupport{
private User user;
set,get方法
public String execute() throws Exception{
if(getUser().getName().equals(" ") && getUser().getPass().equals(" ")){
addActionMessage("转换成功");
return SUCCESS;
}
addActionMessage("转换失败");
return ERROR;
}
}
login.jsp
<s:form action="login">
<s:textfield name="user.name" label="用户名"/>
<s:textfield name="user.pass" label="密码"/>
<s:submit value="转换"/>
struts2会把user.name参数的值赋值给Acion实例的user属性的name属性,user.pass参数同理。
</s:form>
struts2将普通请求参数转换成复合类型对象时需要注意:
- 因为struts2将通过反射来创建一个复合类(User类)的实例,因此系统必须为该复合类提供无参数的构造器。
- 如果希望使用user.name请求参数的形式为Action实例的user属性的name属性赋值,则必须为user属性对应的复合类(User类)提供setName()方法,因为struts2是通过调用该方法来为该属性赋值的。当然Action类中还应该包含getUser()方法。
public class LoginAction extends ActionSupport{
private Map<String,User> users;
set,get方法
public String execute() throws Exception{
if(getUsers().get("one").getName().equals(" ") && getUsers().get("one").getPass().equals(" ")){
addActionMessage("登录成功");
return SUCCESS;
}
addActionMessage("登录失败");
return ERROR;
}
}
login.jsp
<s:form action="login">
<s:textfield name="users['one'].name" label="第one个用户名"/>
<s:textfield name="users['one'].pass" label="第one个密码"/>
<s:textfield name="users['two'].name" label="第two个用户名"/>
<s:textfield name="users['two'].pass" label="第two个密码"/>
<s:submit value="登录">
</s:form>
将表单域的name属性设置为”Action属性名[‘key值’].属性名”,其中”Action属性名”是Action类型包含的Map类型属性,后一个属性名则是Map对象里复合类型对象的属性名。
如果将LoginAction中user属性改为List<User>
,一样可以利用OGNL表达式做到,只要通过索引来指定要将请求参数转换成List的哪个元素。
<s:form action="login">
<s:textfield name="user[0].name" label="第一个用户名"/>
<s:textfield name="user[0].pass" label="第一个密码"/>
<s:textfield name="user[1].name" label="第二个用户名"/>
<s:textfield name="user[1].pass" label="第二个密码"/>
</s:form>
3.指定集合元素的类型
前面使用集合时都使用了泛型,这种泛型可以让struts2了解集合元素的类型,struts2就可通过反射来创建对应类的对象,并将这些对象添加到List中。
如果不使用泛型,struts2允许开发者通过局部类型转换文件来指定集合元素的类型。类型转换文件就是一个普通的Properties(*.properties)文件。
public class LoginAction extends ActionSupport{
private List users;
set,get方法
public String execute() throws Exception{
//因为没有使用泛型,所以要进行强制类型转换
User firstUser = (User)getUsers().get(0);
if(firstUser.getName().equals(" ") && firstUser.getPass.equals(" ")){
addActionMessage("登录成功");
return SUCCESS;
}
addActionMessage("登录失败");
return ERROR;
}
}
局部类型转换文件的文件名为ActionName-conversion.properties类型转换文件应该放在和Action类文件相同的位置。
为了指定List集合里元素的数据类型,需要指定两个部分:
1. List集合属性的名称。
2. List集合里元素的类型。
通过在局部类型转换文件中指定key-value对:
Element_<ListPropNmae>=<ElementType>
ListPropName替换成List集合属性的名称、ElementType替换成集合元素的类型,如下:
Element_users=com.hyq.User
如果对于Map类型的属性,则需要同时指定Map的key类型和value类型。为了指定Map类型属性key属性,应该在局部类型转换文件中增加:
`Key_<MapPropName>=<KeyType>
Key是固定的,MapPropName是Map类型属性的属性名,复合类型指定的是Map的key值的全限定类型。
value类型:
Element_<MapPropName>=<ValueType>
Element是固定的,MapPropName是Map类型属性的属性名,复合类型指定的是Map的key值的全限定类型。
4.自定义类型转换器
需要把一个字符串转换成一个复合对象(例如User对象)时,这就需要使用自定义类型转换器。
public class LoginAction extends ActionSupport{
private User user;
set,get方法
if(getUser().getName().equals(" ") && getUser().getPass().equals(" ")){
addActionMessage("转换成功");
return SUCCESS;
}
addActionMessage("转换失败");
return ERROR;
}
struts2的类型转换器实际上依然是基于OGNL框架的,在OGNL项目中有一个TypeConverter接口,这个接口就是自定义类型转换器必须实现的接口。
public interface TypeConverter{
public Object convertValue(Map context,Object target,Member member,String propertyName,Object value,Class toType);
}
实现类型转换器必须实现上面的TypeConverter,不过上面接口里的方法太复杂,所以OGNL项目还为该接口提供了一个实现类:DefaultTypeConverter,通常都采用扩展类来实现自定义类型转换器。实现自定义类型转换器需要重写DefaultTypeConverter类的converType()方法。
UserConverter.java
public class UserConverter extends DefaultTypeConverter{
public Object convertValue(Map context,Object value,Class toType){
if(toType == User.class){
//系统的请求参数是一个字符串数组
String[] params = (String[]) value;
User user = new User();
//只处理请求参数数组第一个数组元素
//并将该字符串以英文逗号分割成两个字符串
String[] userValues = params[0].split(",");
user.setName(userValues[0]);
user.setPass(userValues[1]);
return user;
}else if(toType == String.class){
User user = (User)value;
return "<" + user.getName() + "," + user.getPass + ">";
}
return null;
}
}
1. convertValue 方法的作用
该方法负责完成类型的转换,不过这种转换时双向的:当需要把字符串转换成User实例时,是通过该方法完成的;当需要把User实例转换成字符串时,也是通过该方法完成的。程序通过toType的类型即可判断转换的方向。
2. conterValue方法参数和返回值的意义
第一个参数:context是类型转换环境的上下文。
第二个参数:value是需要转换的参数,随着转换方向的不同,value参数的值也是不一样的。
第三个参数:toType是转换后的目标类型。
转换器的convertValue方法,接受到需要转换的值,需要转换的目标类型为参数,然后返回转换后的目标值。
3.当把字符串向User类型转换时,为什么value是一个字符串数组,而不是一个字符串。
因为一个下拉框可以选择多个值,这是下拉框对应的请求参数则是字符串。
对于DefaultTypeConverter转换器而言,它必须考虑到最通用的情形,因此将所有的请求参数都视为字符串数组。
可以认为DefaultTypeConverter是通过HttpServletRequest的getParameterValues(name)方法来获取请求参数值的。
5.注册类型转换器
- 注册局部类型转换器:局部类型转换器仅仅对某个Action的属性起作用。
- 注册全局类型转换器:全局类型转换器对所有Action的特定类型的属性都会生效。
使用JDK1.5的注解来注册类型转换器:通过注解方式来注册类型转换器
- 局部类型转换器
注册局部类型转换器使用局部类型转换文件指定,只要在局部类型转换文件中添加:
<propName>=<ConverterClass>
user=com.hyq.UserConverter
propName替换成需要进行类型转换的属性、ConverterClass替换成类型转换器的实现类即可。
局部类型转换器只对指定Action的特定属性起作用,有很大的局限性。通常会将类型转型器注册成全局类型转换器,让该类型转换器对该类型的所有属性起作用。 - 全局类型转换器
全局类型转换器不是对指定Action的指定属性起作用,而是对指定类型起作用。
注册全局类型转换器应该提供一个xwork-conversion.properties文件,该文件直接放在WEB-INF/classes路径下。
com.hyq.vo.User = com.hyq.UserConverter
该全局类型转换器就会对所有类型为com.hyq.vo.User类型的属性起作用。 - 局部类型转换器和全局类型转换器的说明
局部类型转换器是对指定Action的指定属性进行转换,该转换器的转换方法对该属性只转换一次。
全局类型转换器会对所有Action的特定类型进行转换,全局类型转换将不是对该集合属性整体进行转换,而是对该集合属性的每个元素进行转换。
- 局部类型转换器
6.基于struts2的自定义类型转换器
为了简化类型转换器的实现,struts2提供了一个StrutsTypeConverter抽象类,这个抽象类是DefaultTypeConverter类的子类。StrutsTypeConverter类简化了类型转换器的实现,该类已经实现了DefaultTypeConverter的convertValue()方法。当需要将字符串转换成复合类型时,调用convertFormString()抽象方法;当需要把复合类型转换成字符串时,调用convertToString()抽象方法。
public class UserConverter extends StrutsTypeConverter{
public Object convertFromString(Map context,String[] values,Class toClass){
User user = new User();
String[] userValues = values[0].split(",");
user.setName(userValues[0]);
user.setPass(userValues[1]);
return user;
}
public String convertToString(Map context,Object o){
User user = (User)o;
return "<" + user.getName() + "," + user.getPass() + ">";
}
}
7.处理Set集合
通常不建议在Action中使用Set集合属性,因为Set集合里元素处于无序状态。除非Set集合里的元素有一个标识属性,这个属性可以唯一地表示集合元素,这样struts2就可以根据该标识属性类存取集合元素。
public class LoginAction extends ActionSupport{
private Set users;
set,get方法
}
public class UserConverter extends StrutsTypeConverter{
public Object convertFromString(Map context,String[] values,Class toClass){
Set result = new HashSet();
for(int i = 0;i < values.length;i++){
User user = new User();
String[] userValues = values[0].split(",");
user.setName(userValues[0]);
user.setPass(userValues[1]);
result.add(User);
}
return result;
}
public String convertToString(Map context,Object o){
if(o.getClass() == Set.class){
Set user = (Set)o;
String result = "[";
for(Object obj : users){
User user = (User)obj;
result += "<" + user.getName() + "," + user.getPass() + ">";
}
return result + "]";
}else{
return "";
}
}
}
public class User{
private String name;
private String pass;
set,get方法
public boolean equals(Object obj){
if(this == obj){
return true;
}
if(obj != null && obj.getClass() == User.class){
User user = (User)obj;
return this.getName().equals(user,getName());
}
return false;
}
public int hashCode(){
return name.hashCode();
}
}
重写了equals和hashCode两个方法,可以看出,该User类的标识属性是name,当两个User的name相等时即可认为它们相等。
struts2通过局部类型转换文件来指定Set集合元素的标识属性,在局部类型转换文件中增加:
KeyProperty_<SetPropName>=<keyPropName>
users = com.hyq.UserConverter
KeyProperty_users=name
一旦指定了集合元素的索引属性后,struts2就可以通过该索引属性来存取Set集合元素:
<s:property value="users('huang').name"/>
8.类型转换中的错误处理
(1. )处理类型转换错误
为了让Struts2类型转换的错误处理机制生效,包括下一节的输入校验生效,都必须让Action继承Struts2的ActionSupport基类,因为ActionSupport负责收集类型转换错误,输入校验错误,并将它们封装成FieldError对象,添加到ActionContext中
对于中文环境,出入中文提示,需要在国际化资源文件中增加:
xwork.default.invalid.fieldvalue={0}字段类型转换失败!
上面的资源文件中包含了非西欧字符,因此必须使用native2ascii命令来处理该文件。
在某些时候,可能还需要对特定字段指定特别的提示信息,此时可通过Action的局部资源文件来实现,增加:
invalid.fieldvalue.<propName>=<tipMsg>
invalid.fieldvalue.user.birth=生日信息必须满足yyyy-MM-dd格式
- propNmae替换成需要进行类型转换的属性名(此处支持OGNL表达式)
- tipMsg替换转换失败后的提示信息。
(2.) 处理集合属性的转换错误
public class LoginAction extends ActionSupport{
private List<User> user;
set,get方法
}
上面Action中的users是一个List集合,此处有两种方式为users传入请求参数:
- 只传入一个users请求参数,该请求参数的值是字符串数组的形式。
分别传入多个user[0]、user[1]···形式的请求参数,这种形式将会充分利用OGNL表达式类型转换机制。
对于第一种形式,因为只有一个请求参数,请求参数名为users,只要任何一个users请求参数不能成功转换成User对象,Struts2都会提示users字段无效。
第二种形式:<s:form action="login"> <s:iterator value="{0,1,2}" status="stat"> <s:textfield name="users[%{#stat.index}]" label="第%{#stat.index}个用户信息"> </s:iterator> <s:submit value="转换"/> </s:form>
上面页面使用了迭代器标签来指定三个表单域的name,三个表单域的name将分别是users[0]、users[1]、users[2],在这种情况下如果任何一个表单域类型转换失败,都会出现提示字段。