深入理解Struts2----类型转换
之前的一系列文章主要介绍了有关Struts2的一些基本用法和部分的简单原理,但是始终没有介绍有关拦截器的相关内容,从本篇开始我们将从另一个角度去深入理解框架的使用,核心还是拦截器,但本篇首先来介绍下有关框架中类型转换的相关内容。主要包含以下几小节:
- 类型转换的使用场景
- Struts2内默认转换器
- 基于OGNL的类型转换
- 自定义类型转换
- 注册类型转换器
- 类型转换的错误处理
一、类型转换的使用场景
何谓类型转换?类型转换就是指我们在客户端使用GET/POST或者action标签的转发到某个具体的Action实例中的时候,我们传入的参数可以自动转换为Action实例的实例属性的值的一个过程。下面看一个熟悉的例子,回顾下:
//login登录页面
<body>
<form method="post" action="login">
姓名:<input type="text" name="username" /><br />
密码:<input type="password" name="password" /><br />
<input type="submit" value="提交"/>
</form>
</body>
//login
public class LoginAction extends ActionSupport {
private String username;
private int password;
public void setUsername(String name){
this.username = name;
}
public String getUsername(){
return this.username;
}
public void setPassword(int pa){this.password = pa;}
public int getPassword(){
return this.password;
}
public String execute() throws Exception{
return SUCCESS;
}
}
//index页面
<body>
<h1>this is the index page</h1>
<p><s:property value="username"/></p>
<p><s:property value="password"/></p>
<br/>
<s:debug/>
</body>
我们首先在login页面输入两个表单的数值,然后转发到LoginAction ,执行execute方法,拉取index页面的内容,输出结果如下:
我们虽然没有显式的为LoginAction 的两个实例属性赋值,但是在index页面中我们依然可以获取到该属性的值,他们的值对应于login表单页面提交过来的值,也就是说从表单页面提交到LoginAction 的时候会自动根据名称传值。这就是类型转换,从表单页面的String类型转换为LoginAction 中对应的属性的类型,但是这种自动转换并不是总是生效的,具体我们接着看。
二、Struts2内默认转换器
表单中所有输入的值都将作为String类型提交到相应的Action,至于如何将这些String类型转换为Action中的属性的类型是需要做一些判断的,Struts2中默认有一个类型转换器,可以帮助我们完成大部分的自动转换操作。其支持的从String类型转换的目标类型如下:
- boolean和Boolean:字符串true会转换为布尔类型值true
- char和Character:字符串转字符
- int和Integer:字符串转整型类型
- long和Long:字符串转长整型
- float和Float:字符串转单精度浮点型
- double和Double:字符串转双精度浮点型
- Date:字符串转日期类型,需要字符串满足一定的格式
- 数组:多个input表单提交给同一个Action的属性,就会构成一个数组传入到该属性中
- 集合:和数组类似,需要指定了的类型,并且类型不能超出基本数据类型
对于我们在Action中声明的属性的类型,如果是以上的这些类型的话,那么从客户端提交的过来的字符串就可以默认使用该机制自动转换成对应的类型,完成自动赋值。如果不是上述的类型,那么就需要自定义类型转换器来实现显式的转换类型,该内容后文介绍。此处只需要知道Action中的属性的类型为上述的几种,则从表单页面传入的数值会自动根据属性名自动完成赋值。
三、基于OGNL的类型转换
对于非基本类型,我们使用默认的转换机制是不能解决问题的,例如修改上述的LoginAction:
//其中walker是一个符合Javabean规范的类,其具有两个属性name和age
public class LoginAction extends ActionSupport {
private Walker walker;
public void setWalker(Walker w){
this.walker =w;
}
public Walker getWalker(){
return this.walker;
}
public String execute() throws Exception{
return SUCCESS;
}
}
如果Action实例的一个属性是我们自定义的类型,那么login表单页面原有的代码肯定是不能生效的,因为你指定的username和password在Action实例中是没有的。那么我们怎么将一个字符串赋值给Action实例属性呢?ognl语法是可以做到的,例如:
//login页面,使用表单标签
<body>
<s:form method="post" action="/login">
<s:textfield name="walker.username" label="用户名"/>
<s:textfield name="walker.age" label="年龄"/>
<s:submit value="提交"/>
</s:form>
</body>
//index页面
<body>
<h1>this is the index page</h1>
<p><s:property value="walker.getUsername()"/></p>
<p><s:property value="walker.getAge()"/></p>
<br/>
<s:debug/>
</body>
我们在login页面使用ognl语法,walker.username指定了为Action实例属性walker的username属性传值,walker.age指定了为Action实例属性的walker的age属性传值。其实我们到这里可以看出来,使用ognl语法可以实现非基本类型的转换,实际上还是将问题转化到我们讨论的第一种情况,也就是把这么一个问题:如何将一个String类型转换为非基本类型,转化为了:如何把一个String类型转化为非基本类型的属性的类型。而这种问题Struts已经帮我们解决了。下面是上述程序的运行截图:
有关该分类还需要说明一点的是:对于list和map集合,这里的操作是有些变化的。我们详细看看:
//修改属性为一个list集合
public class LoginAction extends ActionSupport {
private List<Walker> list;
public void setList(List<Walker> w){
this.list =w;
}
public List<Walker> getList(){
return this.list;
}
public String execute() throws Exception{
return SUCCESS;
}
}
//修改了的login表单页面
<body>
<s:form method="post" action="/login">
<s:textfield name="list[0].username" label="用户名"/>
<s:textfield name="list[0].age" label="年龄"/>
<s:textfield name="list[1].username" label="用户名"/>
<s:textfield name="list[1].age" label="年龄"/>
<s:submit value="提交"/>
</s:form>
</body>
LoginAction 中的改动没什么需要说的,至于login页面中使用了list[0].username表示为Action属性list的第一个元素的username传值,相同的,list[0].age表示为Action属性的list的第一个元素的age属性传值。index页面遍历list的代码没有贴出,因为比较简单。本质上也是和上述介绍的一样,最后都是使用了Struts的默认转换器。下面是输出结果:
上述介绍的是list集合作为Action属性的情况,对于map集合作为Action实例属性的情况其实是类似的,只是在传值和遍历的方面有细微差别。
//修改后的LoginAction 页面
public class LoginAction extends ActionSupport {
private Map<String,Walker> map;
public void setMap(Map<String,Walker> w){
this.map =w;
}
public Map<String,Walker> getMap(){
return this.map;
}
public String execute() throws Exception{
return SUCCESS;
}
}
//login页面的表单传值
<body>
<s:form method="post" action="/login">
<s:textfield name="map['1'].username" label="用户名"/>
<s:textfield name="map['1'].age" label="年龄"/>
<s:textfield name="map['2'].username" label="用户名"/>
<s:textfield name="map['2'].age" label="年龄"/>
<s:submit value="提交"/>
</s:form>
</body>
map['1'].username表示为Action实例的map属性添加一条信息:key为1,key为1的value值为walker的username属性的值为该文本框的值。age属性类似。
四、自定义类型转换
上一小节,我们使用ognl语法可以完成对非基本类型的转换,但是本质上还是调用了Struts的默认转换器。虽然利用ognl语法,我们可以完成大部分的类型转换,但是在某些极端情况下,这种方式是不能解决问题的,此时我们可以考虑自定义一个类型转换器来解析类型转换。想要自定义一个类型转换器就必须继承TypeConverter这个接口并实现其中的唯一方法:
public abstract Object convertValue(Map<String, Object> paramMap, Object paramObject1, Member paramMember, String paramString, Object paramObject2, Class paramClass);
该方法相当复杂,光参数就有七个。好在框架为我们提供了一个默认实现类:DefaultTypeConverter。该抽象类实现了TypeConverter接口并默认实现了一些方法,我们在自定义自己的类型转换器的时候只需要重写该类的某个方法即可,大大降低了我们的开发成本。当然我们可以进去简单看看DefaultTypeConverter抽象类的内部结构:
public Object convertValue(Map<String, Object> context, Object target, Member member, String propertyName, Object value, Class toType)
{
return convertValue(context, value, toType);
}
public Object convertValue(Map<String, Object> context, Object value, Class toType)
{
return convertValue(value, toType);
}
public Object convertValue(Object value, Class toType)
{
......
}
代码比较多,只贴出一些关键性的代码。该抽象类为我们提供了三个convertValue方法重载,他们之间的关系就是:参数多的重载调用参数少的。最后convertValue(Object value, Class toType)方法提供了默认实现,如果目标类型toType是我们上述介绍的几种基本类型,那么直接将value转换成该类型返回,如果value是个数组类型并且目标类型toType也是个数组类型,那么会获取value中的每个元素递归的调用该方法,为当前元素实现类型转换,最后返回toType类型。这里可能文字说明不能很明朗的会意你,你需要辅助着源代码。
对于默认的实现,我们还是不能完成某些自定义类型的转换,毕竟它只是一个默认实现。因为当系统无法使用默认类型转换器实现类型的转换的时候就会去查找是否有自定义的类型转换器,有则会自动调用convertValue最多参数的重载。所以我们可以重写convertValue的任意一个重载来完成自定义类型转换器。下面我们看一段代码:
public class WalkerConvert extends DefaultTypeConverter {
public Object convertValue(Object value, Class toType){
if(toType == Walker.class){
String[] params = (String[])value;
Walker w = new Walker();
String[] user = params[0].split(",");
w.setUsername(user[0]);
w.setAge(Integer.valueOf(user[1]));
return w;
}
return null;
}
}
这里我们定义了一个WalkerConvert 类继承自DefaultTypeConverter 并重写了该convertValue方法。该方法具有两个参数,第一个参数表示原类型,第二个参数表示目标类型。这里需要对第一个参数value做一点说明,该参数的值实际上是一个String数组,一般情况下我们的参数被存放在索引位置为0的元素中,其余元素内容只有在表单是下拉框的时候将所有下拉框中的选项传过来(如果不使用下拉框一般只用到该数组的第一个元素)。上述代码中,我们将传入的字符串按照逗号分隔,前半部分是username的值,后半部分是age的值,我们看下结果图:
当我们从表单中提交我们填入的字符串,到了Action中之后,由于默认转换器不能完成自动转换,于是框架查找是否具有自定义的转换器,找到之后调用convertValue返回的结果就是属性walker的值,最后我们在index页面输出该walker属性的两个子属性。该方法中的操作我们已经介绍过了,此处不再赘述。当然此处还有一些疑问,例如:定义的该WalkerConvert 该放在什么位置,以及它是如何被web应用加载的?等。这些问题我们将在下一小节详细说明。
五、注册类型转换器
带着上一小节的疑问,我们看如何让web容器知道我们的自定义转换器,并在无法使用默认转换器实现转换的时候查找到我们自己定义的转换器。注册一个类型转换器主要有以下三种方式:
- 在局部范围内注册一个类型转换器
- 在全局范围内注册一个类型转换器
- 使用注解注册一个类型转换器
局部注册一个类型转换器实际上只能对某个Action的属性生效。首先我们需要创建一个局部类型转换文件,该文件的命名规则如下:
ActionName-conversion.properties
例如我们在LoginAction中有一个属性为Walker类型,我们需要注册一个该Action的转换器,则命名如下:
LoginAction-conversion.properties
这是该文件的文件名,对于文件内容,比如我们需要为Walker类型注册转换器,则可以在上述文件中添加如下一行代码:
// 属性名=转换器类的位置
walker=MyPackage.WalkerConvert
最后需要补充一点的是,创建的该文件应该和对应的Action位于同一个包下,这是方便框架搜索。
如果想要注册一个全局范围的类型转换器,那么对于该应用的任意一个Action中,只要存在指定的属性,都会调用该转换器实现转换,这是与局部转换器不同之处。注册全局类型转换器需要提供一个文件,该文件名称如下:
xwork-convertion.properties
为某个属性注册类型转换器的代码是一样的,只是该文件可以在全局使用。以上便简单介绍了注册类型转换器的两种方式,至于使用注解注册也是很简单的。此时,我们知道一旦表单页面传入的字符串不能被默认转换器自动转换成相应的类型,那么会查找相应的自定义转换器,返回该属性的值。
六、类型转换的错误处理
最后有关类型转换这块还有一个错误处理的内容没有介绍,其实框架为我们在拦截器栈中注册了一个拦截器:convertionError。该拦截器专门用于拦截类型转换异常,一旦该拦截器拦截到异常产生则会封装所有的异常信息到ActionContext中,然后跳转到input处理结果页面,所以一般我们在为Action添加处理结果的时候会为其添加一个input页面。下面看一个错误处理的示例:
//input.jsp
<html>
<head>
<title></title>
</head>
<body>
<h1>this is the input page</h1>
</body>
</html>
我们只需要为LoginAction添加一个input的处理结果即可,当发生类型转换失败的时候就会封装错误信息并跳转到input页面。以下是程序运行的部分截图:
我们将第二个参数传入一个字符串类型,则必然发生类型转换错误,此时我们看到结果转向了input页面。
至此,我们简单介绍了struts2中有关类型转换的相关内容,有些地方理解不到,总结的不好,望不吝赐教。
深入理解Struts2----类型转换的更多相关文章
- 简单理解Struts2中拦截器与过滤器的区别及执行顺序
简单理解Struts2中拦截器与过滤器的区别及执行顺序 当接收到一个httprequest , a) 当外部的httpservletrequest到来时 b) 初始到了servlet容器 传递给一个标 ...
- Struts2系列笔记(7)---Struts2类型转换
Struts2类型转换 struts2中内置了大量的类型转换器用来完成数据类型转换的问题,这篇随笔主要通过两个方面来写Struts类型转换 1:Struts2内置的类型转换器 2:如何自定义 ...
- 14.怎样自学Struts2之Struts2类型转换[视频]
14.怎样自学Struts2之Struts2类型转换[视频] 之前写了一篇"打算做一个视频教程探讨怎样自学计算机相关的技术",优酷上传不了.仅仅好传到百度云上: http://pa ...
- struts2类型转换
1. Struts2中的类型转换 我们知道通过HTTP提交到后台的数据,都是字符串的形式,而我们需要的数据类型当然不只字符串类型一种.所以,我们需要类型转换! 在Struts2中,类型转换的概念除了用 ...
- struts2 类型转换
概述 从一个 HTML 表单到一个 Action 对象, 类型转换是从字符串到非字符串. 在 struts2 中, 把请求参数映射到 action 属性的工作由 Parameters 拦截器负责, ...
- struts2类型转换与校验总结
1.struts2的类型转换分为全部变量转变和局部变量转变. 2.struts2对8中常见的基本类型的属性变量,可以自动转换.如果是User对象,可以手动简历UserAction-coversion. ...
- struts2类型转换中的错误处理
由于类型转换过程中有可能出现原始参数无法转换为目标类型的错误,所以struts2提供了类型转换中的异常处理机制. 在struts2的默认配置文件struts-default.xml中有如下一段配置代码 ...
- java学习笔记 (2) —— Struts2类型转换、数据验证重要知识点
1.*Action.conversion-properties 如(point=com.test.Converter.PointListConverter) 具体操作类的配置文件 2.*Action. ...
- struts2 之 struts2类型转换
1. 在struts2中,相比servlet来时,获取数据时,程序员没有进行手动的类型转换,类型转换工作都有struts2来完成处理,但愿对于自定义类型数据,struts2不会帮助我们完成类型转换工作 ...
- 【学习笔记】Struts2 类型转换
为什么需要类型转换 在基于HTTP协议的Web应用中 客户端请求的所有内容(表单中提交的内容等)都以文本编码的方式传输到服务器端但服务器端的编程语言(如Java)有着丰富的数据类型 如 int boo ...
随机推荐
- JavaWeb之HTTP协议
一.概念 协议是指计算机通信网络中两台计算机之间进行通信所必须共同遵守的规定或规则,超文本传输协议(HTTP)是一种通信协议,它允许将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器. ...
- 1113: 零起点学算法20——输出特殊值II
1113: 零起点学算法20--输出特殊值II Time Limit: 1 Sec Memory Limit: 64 MB 64bit IO Format: %lldSubmitted: 207 ...
- Python爬虫 Cookie的使用
Cookie,指某些网站为了辨别用户身份.进行session跟踪而储存在用户本地终端上的数据(通常经过加密) 比如说有些网站需要登录后才能访问某个页面,在登录之前,你想抓取某个页面内容是不允许的.那么 ...
- activiti 5.13 使用activiti设置用户组任务的 工作流的角色
1.设置activiti 流程引擎的用户,组别,关系/**在部署流程定义和启动流程实例的中间,设置组任务的办理人,向Activiti表中存放组和用户的信息*/ IdentityService iden ...
- swust oj(0088)表达式的转换
表达式的转换(0088) Time limit(ms): 5000 Memory limit(kb): 65535 Submission: 435 Accepted: 93 Accepted 16级卓 ...
- 1029. Median
Given an increasing sequence S of N integers, the median is the number at the middle position. For e ...
- Go 并发随机打印1-n
package main import ( "fmt" "math/rand" "sync" "t ...
- CLR via C#中的一个多线程例子
parallel的For和ForEach方法有一些重载版本允许传递三个委托 1.任务局部初始化委托(localInit),未参与工作的每一个任务都调用一次委托,在任务被要求处理前调用. 2.主体委托( ...
- HYML / CSS和Javascript 部分
1 CSS实现垂直水平居中 HTML结构: <div class="wrapper"> <div class="content">&l ...
- 仿:Android - 微信 - 朋友圈 - 小视频播放,多 4 句废话算我输
作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:http://www.cnblogs.com/linguan ...