struts2 可以将符合ognl 表达式的字符串内容转换为action中所对应的属性。
简单的方式就不说了,平常都用烂了,比如在action中有个 private Model model 属性,该属性的结构为
public class model{
private String name;
private String address;
get(){}
set(){}
}
这就是一个pojo 对象,一般在jsp页面的文本标签中 的name属性中 我们就只需要 model.name 和model.address ,struts2默认的类型转换器就会自动将内容封装到model属性中,确实很方便。
现在我的需求是 要将jsp页面中一组model 封装到action中的 List<model> list 中去?
在网上找的资料多数是要求配置局部转换文件即ActionName-conversion.properties,虽然可行,但却是很麻烦,实际上struts2是不需要任何配置,就可以实现对上述需求的转换的。
1.首先对于集合,list,数组 必须有泛型格式,如果没有使用泛型,那么struts2 就不知道如何解析和封装数据,所以才会用到上面的配置文件。
2.其次,jsp页面中ognl表达式的写法才是关键。正确的写法应该是:
<s:textfield name="list[0].name"></s:textfield> ( 与struts2的标签无关系)
<s:textfield name="list[0].adress"></s:textfield>
<s:textfield name="list[1].name"></s:textfield>
<s:textfield name="list[1].adress"></s:textfield>
注意 一定要有类似于数组的下标,如果没有 也不会报错,测试后你会发现,struts2会将上面的标签全部封装成四个model对象,所以讲到这里你就明白了,为什么要加上下标,简单的将就是可以依靠下标 来确定将该属性 封装到哪个实体中去。
由此 struts2处理ognl表达式和action中属性封装的过程:根据请求过来的参数名称在action 中查找对应的属性。比如list[0].address这个请求参数,其实就是一个拼接getList()方法的过程 如果返回的有该对象 则说明有该属性,同时struts2还会检查用户是否注册了该类型解析器,如果有的话,就是用该解析器来解析获得的参数内容。否则就是用默认的方式即get set方法来封装参数。
一般都是解析页面控件的 name 属性,来获取ognl表达式,根据这个表达式来查找action中对应的类型。
注册局部类型转换器:局部转换器仅仅对某个action的属性起作用。
·注册全局类型转换器:全局类型转换器对所有的Action的特定类型的属性都会生效。
1、局部类型转换器
提供如下格式的文件
文件名: ActionName-conversion.properties
内容:多个propertyName(属性名)=类型转换器类(含包名),如 date=com.aumy.DateConverter
存放位置:和ActionName类相同路径。
2、全局类型转换器
提供如下格式的文件
文件名: xwork-conversion.properties
内容:多个“复合类型=对应类型转换器”项组成,如 java.Util.Date=com.aumy.DateConverter
存放位置:WEB-INF/classes/目录下。
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
11.2.1简单类型
Struts2已经内置了基本数据类型及其包装类和其他一些常见的用于表示数字/日期类型的类型转换器,包括:
- int/Integer:整数型
- short/Short:短整数型
- long/Long:长整型
- float/Float:浮点型
- double/Double:双精度型
- boolean/Boolean:布尔型
- byte/Byte:字节型
- char/Character:字符型
- BigInteger:大整数型
- BigDecimal:大浮点数型
- Date:日期型
11.2.2枚举类型
枚举类型往往被人们忽略,其实Struts2的Action中也可以直接使用枚举类型,其对应的参数值只需要对应枚举定义时的定义名即可。
比如有如下的枚举定义,示例代码如下:
public enum ColorEnum {
red,blue,green;
}
在Action引用的时候,只需传入枚举的定义名(red,blue或green)即可,示例代码如下:
public class ConverterAction extends ActionSupport {
private ColorEnum color;
public ColorEnum getColor() {
return color;
}
public void setColor(ColorEnum color) {
this.color = color;
}
public String execute() throws Exception {
System.out.println("color="+color);
System.out.println("传入的颜色是红色吗?答案是 :"+(color==ColorEnum.red));
return SUCCESS;
}
}
其在struts.xml中的定义为:
<package name="helloworld" extends="struts-default">
<action name="converterAction" class="cn.javass.convert.ConverterAction">
<result>/converter/success.jsp</result>
</action>
</package>
为了简单,就不去写页面了,直接通过URL来访问,使用get传参的方式,访问的URL为:“http://localhost:9080/helloworld/converterAction.action?color=red”,运行后,后台输出的结果为:
red
true
可以看到,使用red作为参数将得到枚举中red的那个枚举值。
11.2.3复合类型
这里提到的复合类型与上一节的简单类型相比,并不是Action的属性的类型变得复杂了,而是操作属性的OGNL变得复杂了,其最终操作的无非还是上面的那些简单类型。
其实,这些东西在第四章的时候都已经提到过了,只不过是在这里再次归纳总结,让大家看到类型转换器的全貌。
1:JavaBean
在Action中定义一个JavaBean属性,那么访问JavaBean中的某一个属性,需要使用Action中JavaBean的属性名.JavaBean中的属性名。
比如有长方形这个JavaBean:
public class Rectangle {
private int width;
private int height;
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
在Action定义一个长方形类型的属性,并提供相应的getter/setter方法,示例代码如下:
public class ConverterAction extends ActionSupport {
private Rectangle rectangle;
public Rectangle getRectangle() {
return rectangle;
}
public void setRectangle(Rectangle rectangle) {
this.rectangle = rectangle;
}
//其它的就省略了
}
需要在提交页面上用<s:textfield name=” rectangle.width”/>来引用Action的rectangle属性的width属性。就相当于调用这个Action的get Rectangle().setWidth()方法。
2:数组和List—简单属性
使用数组和List及其相似,在提交页面上并无区别,在Action中也仅是声明的区别。比如在Action中需要得到提交页面提交的两组身高和体重的数据:
public class ConverterAction extends ActionSupport {
private int heights[];
private List<Integer> weights;
public int[] getHeights() {
return heights;
}
public void setHeights(int[] heights) {
this.heights = heights;
}
public List<Integer> getWeights() {
return weights;
}
public void setWeights(List<Integer> weights) {
this.weights = weights;
}
//其它的就省略了
}
对应的提交页面上需要添加:
public class ConverterAction extends ActionSupport {
private Rectangle[] rectangles;
public Rectangle[] getRectangles() {
return rectangles;
}
public void setRectangles(Rectangle[] rectangles) {
this.rectangles = rectangles;
}
//其它的就省略了
}
这个时候,如果在提交页面上不用索引:
<s:textfield name="rectangles.height"/>
<s:textfield name="rectangles.weight"/>
<s:textfield name="rectangles.height"/>
<s:textfield name="rectangles.weight"/>
在Action中得到的就会是四个对象,Struts2并不会把第一个rectangles.height和第一个rectangles.weight凑成一对,放到一个对象里,而是把四个属性都分别放到自己的对象里。
所以,这时候,必须写索引,示例代码如下:
<s:textfield name="rectangles[0].height"/>
<s:textfield name="rectangles[0].weight"/>
<s:textfield name="rectangles[1].height"/>
<s:textfield name="rectangles[1].weight"/>
Action中才能正确的接到两个长方形的对象。
4:Map
Map与数组及其类似,同样是用来存放多个“单体”数据,只不过数组用索引来区分不同的单体,而Map用Key来区分所有的单体。可以用“Map名[‘Key值’]”这样的OGNL来引用Map中的值。
在Action中使用Map来存储多个长方形,多个长方形以Key区分:
public class ConverterAction extends ActionSupport {
private Map<String, Rectangle> map;
public Map<String, Rectangle> getMap() {
return map;
}
public void setMap(Map<String, Rectangle> map) {
this.map = map;
}
//其它的就省略了
}
在提交页面上可以这么写:
<s:textfield name="map[‘first’].height"/>
<s:textfield name="map[‘first’].weight"/>
<s:textfield name="map[‘second’].height"/>
<s:textfield name="map[‘second’].weight"/>
Action的map中。
5:小节
这里看到了很多的复合类型:JavaBean、数组或List、Map,这些复合类型之间还可以结合使用。在前面就已经见到了JavaBean数组(Rectangle[] rectangles);Map里面有JavaBean(Map<String, Rectangle>)。反过来JavaBean里面有List或Map,甚至嵌套更多层都是可以的。
虽然看起来会很复杂,但只要牢牢掌握它们各自的访问方式,然后根据情况自由组合就可以了。基本的访问方式为:
- JavaBean:用“.”来访问自己的属性
- 数组或List:用“[索引]”来访问自己的第几个元素
- Map:用“[‘Key’]”来访问自己的键为Key的元素。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
11.3.1概述
虽然内建类型转换器能满足绝大多数的需求,但是,有的时候还是需要使用自定义类型转换器来实现特定的需求。
还是用前面的长方形来说明,如果用两个文本框分别输入长方形的高和宽,整个类型装换都没有问题,它可以工作的很好。
但是,如果我们想在一个文本框内同时输入宽和高(width:height),形如16:9,这个时候就需要自定义类型转换器了。
11.3.2实现自定义类型转换器
自定义类型转换器的实现非常简单,写一个类,继承StrutsTypeConverter抽象类,该类在org.apache.struts2.util包内,这个抽象类有两个方法需要我们实现:
- public Object convertFromString(Map context, String[] values, Class toClass)方法,实现字符串向对象的转换,它有三个参数:
l context:转换上下文,可以在里面引用各种对象,比如:可以通过context.get(ValueStack.VALUE_STACK)来引用值栈。
l values:用户输入的字符串。
l toClass:将要被转换成的对象类型。
- public String convertToString(Map context, Object o)方法,实现对象向字符串的转换,它有两个参数:
l context:同上,也是转换的上下文。
l o:需要被转换的对象。
实现示例功能的自定义类型转换器,示例代码如下:
public class RectangleConverter extends StrutsTypeConverter {
/**
* 长方形向字符串转换
*/
public Object convertFromString(Map context, String[] values, Class toClass) {
//用户输入,比如16:9
String userInput = values[0];
String[] arr = userInput.split(":");
//在真正的格式转换之前,先把所有的用户输入可能的错误拦截住
if (arr.length!=2){
throw new TypeConversionException("请输入正确的长方形格式如,width:height");
}
try{
Rectangle rectangle = new Rectangle();
int width = Integer.parseInt(arr[0]);
int height = Integer.parseInt(arr[1]);
rectangle.setWidth(width);
rectangle.setHeight(height);
return rectangle;
}catch(RuntimeException e){
throw new TypeConversionException("请输入正确的长方形格式如,width:height",e);
}
}
/**
* 字符串向长方形转换
*/
public String convertToString(Map context, Object o) {
Rectangle rectangle = (Rectangle) o;
return "长方形:宽"+rectangle.getWidth()+",高"+rectangle.getHeight();
}
}
在这里,需要注意在convertFromString方法中,用户输入的字符串很可能是不符合要求的,所有的不符合要求的情况都要抛出TypeConversionException。这是个运行时异常,不需要在方法上声明抛出。
11.3.3注册和引用自定义类型转换器
已经有了自定义类型转换器,应该怎么引用它呢?
1:注册自定义类型转换器
首先要注册这个自定义类型转换器处理。在src下建立一个xwork-conversion.properties文件,这个文件中用“全类名=这个类对应的类型转换器全类名”,来建立类和类型转换器的关系,对于我们来说,只有一行:
cn.javass.convert.Rectangle=cn.javass.convert.RectangleConverter
注册完了之后,整个项目所有的长方形,都可以用自定义的类型转换器来处理了。
2:示例用的Action
Action不需要做任何特殊的处理,也就是使用自定义类型转换器的时候,对Action没有影响,请大家记住。对应的Action的示例代码为:
public class ConverterAction extends ActionSupport {
private Rectangle rectangle;
public Rectangle getRectangle() {
return rectangle;
}
public void setRectangle(Rectangle rectangle) {
this.rectangle = rectangle;
}
public String execute() throws Exception {
return SUCCESS;
}
}
3:修改页面
那么,使用了自定义类型转换器对哪里有影响呢?对页面有影响,既对提交页面有影响,也对展示页面有影响。
在提交页面上,不需要再有两个文本框分别对应长方形的高和宽了,只需要一个文本框,而这个文本框对应的不再是长方形的属性(比如rectangle.width或rectangle.height),而是对应整个长方形对象(rectangle)了,而且这个文本框需要按照自定义类型转换器预定的字符串格式填写,这里定义的是宽:高。
提交页面示例代码如下:
<%@ taglib prefix="s" uri="/struts-tags"%>
<s:form action="/converterAction.action" method="post">
<s:textfield name="rectangle" label="输入长和宽"/>
<s:submit value="提交"/>
</s:form>
显示结果的页面也需要跟随变动,示例代码如下:
- <%@ taglib prefix="s" uri="/struts-tags" %>
- <s:property value="rectangle"/>
<%@ taglib prefix="s" uri="/struts-tags" %>
<s:property value="rectangle"/>
4:测试
运行测试一下,在输入页面的文本框中填入16:9,然后点击提交,Action就可以正确接到这个长方形对象,然后转向输出页面,显示如下:
图11.1 示例自定义转换器
在展示页面上使用的<s:property value="rectangle"/>,就可以显示出自定义类型转换器中convertToString方法的返回值了。
注意:<s:property value="rectangle"/>这句话会引用自定义类型转换器,因为这时候要把一个字符串转换成Rectangle对象,而前面<s:textfield name=” rectangle.width”/>则不会引用自定义转换器,因为它只是要把一个字符串转换成Rectange对象的width属性而已,实际上只需要转换为int。