为什么要有类型转换、谁在做
所有的MVC 框架,都需要负责解析HTTP 请求参数,并将请求参数传给控制器组件。此时问题出现了: HTTP 请求参数都是字符串类型,但Java 是强类型的语言, 因此MVC 框架必须将这些字符串参数转换成相应的数据类型一这个工作是所有的MVC 框架都应该提供的功能。
Struts内置的类型转换
我们都知道在表单输入的数据都是字符串类型,但是Java是强语言类型,所以在Action中操作,必须需要转换,而上面提到的类型,框架会自动帮你处理,你不需要自己插手。
类型转换出错
<s:form action="login1" method="get">
<s:textfield name="user" label="姓名"/>//在这里我们给文本框定义的不是基本类型
<s:submit value="提交"/>
</s:form>
package com.example.test.action;
import com.example.test.bean.User;
import com.opensymphony.xwork2.ActionSupport;
public class LoginAction1 extends ActionSupport {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String execute() throws Exception {
// TODO Auto-generated method stub
return SUCCESS;
}
}
你把一个字符串给了User类型,这肯定gg,让我们看看报错:
看到这个,当时就在想,我去,我明明是类型异常,你怎么搞到了input页面啥,后来百度才知道
通过分析拦截器作用,得知当类型转换出错时,自动跳转input视图 ,在input视图页面中 显示错误信息。
基于OGNL的类型转换
借助于内置的类型转换器,Struts2 可以完成字符串和基本类型之间的类型转换。除此之外,借助于OGNL 表达式的支持,Struts 2 允许以另一种简单方式将请求参数转换成复合类型。对上面的那个问题,我们可以借助OGNL来解决。
<s:form action="login1" method="get">
<s:textfield name="user.username" label="姓名"/>
<s:password name="user.userpassword" label="年龄"/>
<s:submit value="提交"/>
</s:form>
package com.example.test.action;
import com.example.test.bean.User;
import com.opensymphony.xwork2.ActionSupport;
public class LoginAction1 extends ActionSupport {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String execute() throws Exception {
// TODO Auto-generated method stub
return SUCCESS;
}
}
<s:property value="user.username"/>
<s:property value="user.userpassword"/>
上述通过OGNL是如何实现的呢
user.username和user.userpassword的形式一这就是OGNL 表达式的形式,Struts 2 会把user.username参数的值赋值给Action 实例的user 属性的username属性,并将user.userpassword参数的值赋值给Action 实例的user 属性的userpassword属性。
注意:
通过这种方式,Struts 2 可以将普通请求参数转换成复合类型对象,但在使用这种方式时有如下几点需要注意:
- 因为Struts2将通过反射来创建一个复合类(User类) 的实例,因此系统必须为该复合类提供无参数的构造器。(这点特别需要注意,为啥,你如果提供其他的构造函数,默认无参就不带了,这时就会出现问题)
很多时候,细节决定成败呀!!!!
- 必须给复合类的属性添加set、get方法。
基于OGNL的集合的类型转型
集合类型转换:泛型可以让Struts 2了解集合元素的类型,并将这些对象添加到List 中。通过反射来创建对应类的对象。
Map
private Map<String,User> users;
public Map<String, User> getUsers() {
return users;
}
public void setUsers(Map<String, User> users) {
this.users = users;
}
@Override
public String execute() throws Exception {
// TODO Auto-generated method stub
return SUCCESS;
}
<s:form action="login2" method="post">
<s:textfield name="users['one'].username" label="姓名"/>
<s:textfield name="users['one'].userpassword" label="姓名1"/>
<s:textfield name="users['two'].username" label="年龄"/>
<s:textfield name="users['two'].userpassword" label="年龄1"/>
<s:submit value="提交"/>
</s:form>
<s:property value="users['one'].username"/>
<s:property value="users['one'].userpassword"/>
<s:property value="users['two'].username"/>
<s:property value="users['two'].userpassword"/>
Struts 表单数据对map类型数据的绑定:Action 属性名[‘key 值’].属性名(如果key不是String类型,不需要加单引号)
List
private List<User> list;
public List<User> getList() {
return list;
}
public void setList(List<User> list) {
this.list = list;
}
@Override
public String execute() throws Exception {
// TODO Auto-generated method stub
return SUCCESS;
}
<s:form action="login4" method="post">
<s:textfield name="list[0].username" label="姓名"/>
<s:textfield name="list[0].userpassword" label="姓名1"/>
<s:textfield name="list[1].username" label="年龄"/>
<s:textfield name="list[1].userpassword" label="年龄1"/>
<s:submit value="提交"/>
</s:form>
<s:property value="list[0].username"/>
<s:property value="list[0].userpassword"/>
<s:property value="list[1].username"/>
<s:property value="list[1].userpassword"/>
Struts 表单数据对map类型数据的绑定:Action 属性名[索引值].属性(索引值是从0开始,代表第一个对象)
我们对上面的数据绑定方式,来分析,以list作为例子,map和它类似。
表单元素中name,是通过OGNL来绑定的,这点就不多说,但是在获取的时候,为啥list[0].username可以获取呢,查看 查看。
通过list[0].username可以看出是直接访问的OGNL的 value stack 的。
然后我们看到 stack context中:
在参数中,它把集合所有参数,都封装成map,你也可以通过parameters取访问,但是由于在stack value,你必须加#去访问。
<s:property value="#parameters['list[0].username']"/>
<s:property value="#parameters['list[0].userpassword']"/>
<s:property value="#parameters['list[1].username']"/>
<s:property value="#parameters['list[1].userpassword']"/>
指定集合类型
我们知道前面集合能够转换类型成功,因为指定泛型。如果不使用泛型,Struts 2 还知道使用类型转换器来处理该users 属性吗? Struts 2当然不知道! 但Struts 2 允许开发者通过局部类型转换文件来指定集合元素的类型。类型转换文件就是一个普通的Properties (*.properties) 文件,类型转换文件里提供了类型转换的相关配置信息。
<s:form action="login5" method="post">
<s:textfield name="list[0].username" label="姓名"/>
<s:textfield name="list[0].userpassword" label="姓名1"/>
<s:textfield name="list[1].username" label="年龄"/>
<s:textfield name="list[1].userpassword" label="年龄1"/>//在这里有一个问题,这里list为参数,action对应的属性是users,而action一样可以接收,这点很奇怪。按道理这里应该是users才可以的。
<s:submit value="提交"/>
public class LoginAction5 extends ActionSupport {
private List users;
public List getUsers() {
return users;
}
public void setUsers(List users) {
this.users = users;
}
@Override
public String execute() throws Exception {
// TODO Auto-generated method stub
return SUCCESS;
}
}
在这个action中,我们不知道集合属性的users的元素类型,可以采用局部类型转换文件。
编写局部类型转换文件的注意点:
- 局部类型转换文件的文件名应为ActionName-conversion.properties 形式,其中ActionName 是需要Action 的类名,后面的-conversion.properties 字符串则是固定部分。
- 类型转换文件应该放在和Action类文件相同的位置。
- 类型必须全部使用全限定类名。(右击 copy qualified name)
Element_users=com.example.test.bean.User
将上面的 替换成List 集合属性的名称、替换成集合元素的类型即可。
下面以Map为例:
private Map users;
public Map getUsers() {
return users;
}
public void setUsers(Map users) {
this.users = users;
}
@Override
public String execute() throws Exception {
// TODO Auto-generated method stub
return super.execute();
}
<s:form action="login6" method="post">
<s:textfield name="users['one'].username" label="姓名"/>
<s:textfield name="users['one'].userpassword" label="姓名1"/>
<s:textfield name="users['two'].username" label="年龄"/>
<s:textfield name="users['two'].userpassword" label="年龄1"/>
<s:submit value="提交1"/>
配置Map的局部类型转换文件
其中Key 是固定的,是Map 类型属性的属性名,复合类型指定的是Map 的key值的全限定类名。
其中Element是固定的, 是Map类型属性的属性名,复合类型指定的是Map属性的value类型的全限定类名。
Key_users=java.lang.String
Element_users=com.example.test.bean.User
自定义类型转换器
引入:
如何实现自定义类型转化器
Struts 2 的类型转换器实际上依然是基于OGNL 框架的,在OGNL 项目中有一TypeConverter接口,这个接口就是自定义类型转换器必须实现的接口。但是一般使用DefaultTypeConverter接口。
你可以实现任何类型转换,唯独就是不可以String-String ,你这样有啥意义。
重写如下的方法,该方法主要功能就是实现类型的双向转换。
public class UserConverter extends DefaultTypeConverter {
/**
* context 类型转换的上下文的环境
* value 这里的value的值,是可以认为DefaultTypeConverter 是通过HttpServ letRequest 的getParameter Values(name)方法来获取请求参数值的。有两种值的情况,当把字符串转换成复杂的数据类型,该value的值就是字符串数组
* -----在上面这种情况中,我们都知道网络请求参数都是字符串,但是为啥这是字符串数组呢,实则它是为了通用,你一个文本框是只要一个字符串
* 但是有些控件在选择的时候,可能会有多个字符串,这是选择字符串数组的原因
* 当把复杂的数据类型转换成字符串,该value就是复杂数据的实例对象。
* toType 表示转换的目标类型
* 返回值是转换后的值
*/
@Override
public Object convertValue(Map<String, Object> context, Object value,
Class toType) {
// TODO Auto-generated method stub
if(toType==User.class){//字符串类型转换为user类型
String[] params = (String[])value;
String[] uservalue = params[0].split(",");
User user= new User();
user.setUsername(uservalue[0]);
user.setUserpassword(uservalue[1]);
return user;
}else if(toType==String.class){//把user类型转换为字符串
User user = (User) value;
return user.getUsername()+"---"+user.getUserpassword();
}
return super.convertValue(context, value, toType);
}
}
注册类型转换器
我们光定义了类型转换器还不够,因为Struts2 依然不知道什么时候才可以使用它们,所以我们必须要把这些类型转换器注册到Web中。
Struts 2支持注册的三种方式:
- 注册局部类型转换器:仅针对某个Action起作用。
user=com.example.test.util.UserConverter
前者替换成需要进行类型转换的属性、后者替换成类型转换器的实见类即可。
局部类型转换配置文件命名,前缀还是对应Action的名字,后面不变。
可以看的出这种方式的局限只是针对一个action。
局部配置文件应该和对应的action类放在一起。
- 注册全局类型转换器:对所有Action的特定类型属性都生效。
com.example.test.bean.User=com.example.test.util.UserConverter
前者替换成需要进行类型转换的属性类型,这种就不是针对某一个属性了,而是针对该类型的属性,尽管属性名不相同。后者替换成类型转换器的实见类即可。在全局的中,你就不要使用某个属性了,因为局部好从Action中,但是全局不同的Action之间,属性名会重复哦,这点需要注意一下。,所有的都必须是类型,而不是一个确定的Action属性名称,还有全局类型转化文件应该放在src下。
- 使用JDK1.5的注释来注册类型转换器:通过注解方式注册类型转换器。
局部类型转换器对指定Action的指定属性起作用,一个属性只调用convertValue方法一次。 全局类型转换器对所有Action 的特定类型起作用,因此可能对一个属性多次调用convert Value(方法进行转换一一当该属性是一个数组或集合时,该数组或集合中包含几个该类型的元素,那么就会调用convertValue()方法几次。
StrutsTypeConverter
为了简化类型转换器的实现,Struts 2提供了一个StrutsTypeConverter 抽象类,这个抽象类DefaultTypeConverter类的子类。StrutsTypeConverter 类简化了类型转换器的实现, 该类已经实现了DefaultTypeConverter 的convertValue 方法。实现该方法时,它将两个不同转换方向替换成不同方法。
public class UserConverter1 extends StrutsTypeConverter {
/*
* (non-Javadoc)
* @see org.apache.struts2.util.StrutsTypeConverter#convertFromString(java.util.Map, java.lang.String[], java.lang.Class)
* 该方法用于处理String类型转换成复杂数据类型
* String[] arg1 代表的是你请求的字符串参数数组
*/
@Override
public Object convertFromString(Map arg0, String[] arg1, Class arg2) {
// TODO Auto-generated method stub
User u = new User();
String[] stringvalue = arg1[0].split(",");
u.setUsername(stringvalue[0]);
u.setUserpassword(stringvalue[1]);
return u;
}
/*
* (non-Javadoc)
* @see org.apache.struts2.util.StrutsTypeConverter#convertToString(java.util.Map, java.lang.Object)
* 该方法用于复杂数据类型转成String类型
*/
@Override
public String convertToString(Map arg0, Object arg1) {
// TODO Auto-generated method stub
User u = (User) arg1;
return u.getUsername()+u.getUserpassword();
}
}
Set类型转换
不建议在Action中使用Set集合属性,因为Set集合里元素处于无序状态,所以Struts2不能准确地将请求参数转换成Set元素。不仅如此,由于Set集合里元素的无序性,所以Struts 2也不能准确读取Set集合里的元素。除非Set集合里的元素有一个标识属性,这个标识属性可以唯一地表示集合元素,这样Struts2就可以根据该标识属性来存取集合元素了。
<s:form action="login7" method="post">
<s:textfield name="user" label="姓名"/>
<s:submit value="提交"/>
</s:form>
action
private Set user;
public Set getUser() {
return user;
}
public void setUser(Set user) {
this.user = user;
}
@Override
public String execute() throws Exception {
// TODO Auto-generated method stub]
return super.execute();
}
自定义类型转换
public class UserConverter2 extends StrutsTypeConverter {
@Override
public Object convertFromString(Map arg0, String[] arg1, Class arg2) {
// TODO Auto-generated method stub
Set set = new HashSet<User>();
for(int i = 0;i<arg1.length;i++){//考虑你传的参数可能是不止一个字符串的时候
String[] stringvalue = arg1[i].split(",");//每一个字符串以,区别的
User u = new User();
u.setUsername(stringvalue[0]);
u.setUserpassword(stringvalue[1]);
set.add(u);
}
return set;
}
@Override
public String convertToString(Map arg0, Object arg1) {
// TODO Auto-generated method stub
String result = null;
if(arg1.getClass()==Set.class){
Set s = (Set) arg1;
for(Object su:s){
User su1 = (User) su;
result += su1.getUsername()+su1.getUserpassword();
}
return result;
}else{
return null;
}
}
}
采用全局的类型转换
Element_java.util.Set=com.example.test.util.UserConverter2
KeyProperty_java.util.Set=username
和之前不同的主要在于JavaBean的变化,需要重写equals和hashcode方法,通过这两个来将username作为索引,在获取的时候就可以根据username。
public class User {
private String username;
private String userpassword;
public User() {
super();
// TODO Auto-generated constructor stub
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUserpassword() {
return userpassword;
}
public void setUserpassword(String userpassword) {
this.userpassword = userpassword;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
if(this==obj){//如果是同一个对象
return true;
}
if(obj!=null&&obj.getClass()==Set.class){//obj不为空,并且是Set类型
User u = (User) obj;
if(this.username.equals(u.username)){//如果两者姓名一致
return true;
}
}
return false;
}
@Override
public int hashCode() {
// TODO Auto-generated method stub
return username.hashCode();
}
}
<s:property value="user('laohe').username"/>
<s:property value="user('laohe').userpassword"/>
上面访问Set元素用的是圆括号,而不是方括号。但对于数组、List 和Map属性,则通过方括号来访问指定集合元素。
复合类型转换文件
<s:textfield name="user.birth" label="姓名"/>
定义全局文件:
java.util.Date=com.example.test.util.UserConverter1
定义局部文件:
user.birth=com.example.test.util.UserConverter1
类型转换的错误处理
表现层的数据由用户输入,用户的输入是很复杂。比如姓名和年龄之间用#隔开,没有按照这样输入,系统是无法进行数据类型转换。
首先在对应的action下,配置struts自己的过滤器。
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="conversionError"></interceptor-ref>
在上面的默认拦截器栈中包含了conversionError 拦截器的引用,如果Struts 2的类型转换器执行类型转换时出现错误,该拦截器将负责将对应错误封装成表单域错误(FieldError), 并将这些错误信息放入ActionContext中。
处理的流程:
至于类型转换的代码,读者可以参考上面所讲的。还需要在对应的action配置
<result name="input">/input.jsp</result>
配置好以上操作,就可以实现Struts类型转换异常的处理。但是从返回错误信息全是英文,如果要是中文,则需要在Struts配置文件中,配置国际资源文件。
<constant name="struts.custom.i18n.resources" value="global_zh_CN"/>//name是固定,代表国际资源常量名,value是你配置的国际资源文件名。
创建global_zh_CN.propperties
这里创建资源文件,可以是全局的,global_zh_CN.propperties,如果是针对某个action的就用action名称_zh_CN.properties。
invalid.fieldvalue={0}\u7C7B\u578B\u8F6C\u5316\u6709\u8BEF
这种表示方法,不太好,推荐使用下面的。
你在属性文件中输入中文,会自动转码,变成上面的。经过以上,最后一定要把你的jsp的字符编码改成utf-8哦。
我们可以通过
<s:fielderror fieldName="user"/> //指定对应字段的错误信息。
要对特定的字段提供特定的信息,还需要修改资源文件。
invalid.fieldvalue.user.age=\u5E74\u7EA7\u6709\u8BEF
invalid.fieldvalue.user.birth=\u751F\u65E5\u6709\u8BEF
user.birth是action的属性