之前的一系列文章主要介绍了有关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----类型转换的更多相关文章

  1. 简单理解Struts2中拦截器与过滤器的区别及执行顺序

    简单理解Struts2中拦截器与过滤器的区别及执行顺序 当接收到一个httprequest , a) 当外部的httpservletrequest到来时 b) 初始到了servlet容器 传递给一个标 ...

  2. Struts2系列笔记(7)---Struts2类型转换

    Struts2类型转换      struts2中内置了大量的类型转换器用来完成数据类型转换的问题,这篇随笔主要通过两个方面来写Struts类型转换 1:Struts2内置的类型转换器 2:如何自定义 ...

  3. 14.怎样自学Struts2之Struts2类型转换[视频]

    14.怎样自学Struts2之Struts2类型转换[视频] 之前写了一篇"打算做一个视频教程探讨怎样自学计算机相关的技术",优酷上传不了.仅仅好传到百度云上: http://pa ...

  4. struts2类型转换

    1. Struts2中的类型转换 我们知道通过HTTP提交到后台的数据,都是字符串的形式,而我们需要的数据类型当然不只字符串类型一种.所以,我们需要类型转换! 在Struts2中,类型转换的概念除了用 ...

  5. struts2 类型转换

    概述 从一个 HTML 表单到一个 Action 对象, 类型转换是从字符串到非字符串. 在 struts2 中, 把请求参数映射到 action  属性的工作由 Parameters 拦截器负责, ...

  6. struts2类型转换与校验总结

    1.struts2的类型转换分为全部变量转变和局部变量转变. 2.struts2对8中常见的基本类型的属性变量,可以自动转换.如果是User对象,可以手动简历UserAction-coversion. ...

  7. struts2类型转换中的错误处理

    由于类型转换过程中有可能出现原始参数无法转换为目标类型的错误,所以struts2提供了类型转换中的异常处理机制. 在struts2的默认配置文件struts-default.xml中有如下一段配置代码 ...

  8. java学习笔记 (2) —— Struts2类型转换、数据验证重要知识点

    1.*Action.conversion-properties 如(point=com.test.Converter.PointListConverter) 具体操作类的配置文件 2.*Action. ...

  9. struts2 之 struts2类型转换

    1. 在struts2中,相比servlet来时,获取数据时,程序员没有进行手动的类型转换,类型转换工作都有struts2来完成处理,但愿对于自定义类型数据,struts2不会帮助我们完成类型转换工作 ...

  10. 【学习笔记】Struts2 类型转换

    为什么需要类型转换 在基于HTTP协议的Web应用中 客户端请求的所有内容(表单中提交的内容等)都以文本编码的方式传输到服务器端但服务器端的编程语言(如Java)有着丰富的数据类型 如 int boo ...

随机推荐

  1. CSS盒模型和文本溢出

    CSS盒模型和文本溢出 学习目标 认识盒子模型 盒子模型的组成部分 学习盒子模型的相关元素margin padding 文本溢出相关的属性 一.认识盒子模型 盒模型是css布局的基石,它规定了网页元素 ...

  2. JavaScript设计模式读书笔记之一:接口

    接口 在JavaScrip中模仿接口 用注释描述接口 用属性检查模仿接口 用鸭式辨型模仿接口 依赖于接口的设计模式 工厂模式 组合模式 装饰者模式 命令模式 接口 在JavaScrip中模仿接口 用注 ...

  3. Android™ 1.5 android.R.drawable Icon Resources

    图标一览表: http://www.darshancomputing.com/android/1.5-drawables.html 官  方  API: http://developer.androi ...

  4. How To Use ggplot in ggplot2?

    1.What is ggplot2 ggplot2基本要素 数据(Data)和映射(Mapping) 几何对象(Geometric) 标尺(Scale) 统计变换(Statistics) 坐标系统(C ...

  5. 从零开始用 Flask 搭建一个网站(二)

    从零开始用 Flask 搭建一个网站(一) 介绍了如何搭建 Python 环境,以及 Flask 应用基本项目结构.我们要搭建的网站是管理第三方集成的控制台,类似于 Slack. 本篇主要讲解数据如何 ...

  6. redis的安装和测试

    redis一直都是调用别人部署好的,近日想要自己从灵开始搭建一次.其中也生出不少枝节,与各位猿友共同分享,望少走些弯路! 1.提前准备的资源 redis安装包(本人上传到csdn不需积分即可下载): ...

  7. 【stm32】时钟树解析

    有时候会突然忘了这个重要的时钟树,这里转载一个比较好的,以防忘记. STM32时钟系统 在STM32中,有五个时钟源,为HSI.HSE.LSI.LSE.PLL. ①HSI是高速内部时钟,RC振荡器,频 ...

  8. 一行code实现ADO.NET查询结果映射至实体对象。

    AutoMapper是一个.NET的对象映射工具. 主要用途 领域对象与DTO之间的转换.数据库查询结果映射至实体对象. 这次我们说说 数据库查询结果映射至实体对象. 先贴一段代码: public S ...

  9. DirectFB学习笔记四

    本篇目的,实现按钮的点击事件捕获,也就是鼠标点击,如果点击在方框范围内,则响应,在方框外,则忽略. 由于鼠标移动和点击都会产生事件,因此,我们可以在鼠标移动的时候记录坐标,在点击时比较坐标是否在方框范 ...

  10. 带你玩转 jQuery

    一.简介 定义 jQuery创始人是美国John Resig,是优秀的Javascript框架: jQuery是一个轻量级.快速简洁的javaScript库.源码戳这 jQuery对象 jQuery产 ...