以前的一系列文章主要介绍了有关Struts2的一些基本用法和部分的简单原理,可是始终没有介绍有关拦截器的相关内容,从本篇开始咱们将从另外一个角度去深刻理解框架的使用,核心仍是拦截器,但本篇首先来介绍下有关框架中类型转换的相关内容。主要包含如下几小节:html
1、类型转换的使用场景
何谓类型转换?类型转换就是指咱们在客户端使用GET/POST或者action标签的转发到某个具体的Action实例中的时候,咱们传入的参数能够自动转换为Action实例的实例属性的值的一个过程。下面看一个熟悉的例子,回顾下:web
//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 中对应的属性的类型,可是这种自动转换并非老是生效的,具体咱们接着看。框架
2、Struts2内默认转换器
表单中全部输入的值都将做为String类型提交到相应的Action,至于如何将这些String类型转换为Action中的属性的类型是须要作一些判断的,Struts2中默认有一个类型转换器,能够帮助咱们完成大部分的自动转换操做。其支持的从String类型转换的目标类型以下:jsp
对于咱们在Action中声明的属性的类型,若是是以上的这些类型的话,那么从客户端提交的过来的字符串就能够默认使用该机制自动转换成对应的类型,完成自动赋值。若是不是上述的类型,那么就须要自定义类型转换器来实现显式的转换类型,该内容后文介绍。此处只须要知道Action中的属性的类型为上述的几种,则从表单页面传入的数值会自动根据属性名自动完成赋值。post
3、基于OGNL的类型转换
对于非基本类型,咱们使用默认的转换机制是不能解决问题的,例如修改上述的LoginAction:this
//其中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语法是能够作到的,例如:debug
//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已经帮咱们解决了。下面是上述程序的运行截图:code
有关该分类还须要说明一点的是:对于list和map集合,这里的操做是有些变化的。咱们详细看看:orm
//修改属性为一个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属性相似。
4、自定义类型转换
上一小节,咱们使用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应用加载的?等。这些问题咱们将在下一小节详细说明。
5、注册类型转换器
带着上一小节的疑问,咱们看如何让web容器知道咱们的自定义转换器,并在没法使用默认转换器实现转换的时候查找到咱们本身定义的转换器。注册一个类型转换器主要有如下三种方式:
局部注册一个类型转换器实际上只能对某个Action的属性生效。首先咱们须要建立一个局部类型转换文件,该文件的命名规则以下:
ActionName-conversion.properties
例如咱们在LoginAction中有一个属性为Walker类型,咱们须要注册一个该Action的转换器,则命名以下:
LoginAction-conversion.properties
这是该文件的文件名,对于文件内容,好比咱们须要为Walker类型注册转换器,则能够在上述文件中添加以下一行代码:
// 属性名=转换器类的位置 walker=MyPackage.WalkerConvert
最后须要补充一点的是,建立的该文件应该和对应的Action位于同一个包下,这是方便框架搜索。
若是想要注册一个全局范围的类型转换器,那么对于该应用的任意一个Action中,只要存在指定的属性,都会调用该转换器实现转换,这是与局部转换器不一样之处。注册全局类型转换器须要提供一个文件,该文件名称以下:
xwork-convertion.properties
为某个属性注册类型转换器的代码是同样的,只是该文件能够在全局使用。以上便简单介绍了注册类型转换器的两种方式,至于使用注解注册也是很简单的。此时,咱们知道一旦表单页面传入的字符串不能被默认转换器自动转换成相应的类型,那么会查找相应的自定义转换器,返回该属性的值。
6、类型转换的错误处理
最后有关类型转换这块还有一个错误处理的内容没有介绍,其实框架为咱们在拦截器栈中注册了一个拦截器: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中有关类型转换的相关内容,有些地方理解不到,总结的很差,望不吝赐教。