从一个 HTML 表单到一个 Action 对象,类型转换是从字符串到非字符串。
HTTP 没有 “类型” 的概念,每一项表单输入只可能是一个字符串或一个字符串数组。在服务器端,必须把 String 转换为特定的数据类型。
在 struts2 中,把请求参数映射到 action 属性的工作由 params(com.opensymphony.xwork2.interceptor.ParametersInterceptor)拦截器负责, 它是默认的 defaultStack 拦截器中的一员。params拦截器可以自动完成字符串和基本数据类型之间转换。
类型转换错误
如果类型转换失败,系统如何处理?
若 Action 类没有实现 ValidationAware 接口: Struts 在遇到类型转换错误时仍会继续调用其 Action 方法,就好像什么都没发生一样。
若 Action 类实现 ValidationAware 接口:Struts 在遇到类型转换错误时将不会继续调用其 Action 方法:Struts 将检查相关 action 元素的声明是否包含着一个 name=input 的 result。如果有, Struts 将把控制权转交给那个 result 元素;若没有 input 结果,Struts 将抛出一个异常。
类型转换错误消息的定制
作为默认的 default 拦截器的一员,ConversionError 拦截器负责添加与类型转换有关的出错消息(前提: Action 类必须实现了 ValidationAware 接口)和保存各请求参数的原始值。
若字段标签使用的不是 simple 主题,则非法输入字段将导致一条有着以下格式的出错消息:
Invalid field value for field "price".
覆盖默认的出错消息[定制出错消息的内容]
在对应的 Action 类所在的包中新建 ActionClassName.properties 文件,ClassName 即为包含着输入字段的 Action 类的类名。
在ActionClassName.properties 文件中添加如下键值对:
invalid.fieldvalue.fieldname=your error meesage
其中,fieldname为对应的属性的名称。
定制出错消息的样式
如果是 simple 主题, 还会自动显示错误消息吗? 如果不会显示, 怎么办 ?
1). 不会。还可以使用 s:fielderror 标签来显示. 可以通过 fieldName 属性显示指定字段的错误.
2). 通过 debug 标签, 可知若转换出错, 则在值栈的 Action(实现了 ValidationAware 接口) 对象中有一个 fieldErrors 属性.该属性的类型为 Map<String, List<String>> 键: 字段(属性名), 值: 错误消息组成的 List. 所以可以使用 LE 或 OGNL 的方式来显示错误消息: ${fieldErrors.age[0]}
3) 若是 simple 主题, 且使用 <s:fielderror fieldName="age"></s:fielderror> 来显示错误消息, 则该消息在一个 ul, li, span 中. 如何去除 ul, li, span 呢 ?
在 template.simple 下面的 fielderror.ftl 定义了 simple 主题下, s:fielderror 标签显示错误消息的样式. 所以修改该配置文件即可. 在 src 下新建 template.simple 包, 新建 fielderror.ftl 文件, 把原生的 fielderror.ftl 中的内容
复制到新建的 fielderror.ftl 中, 然后剔除 ul, li, span 部分即可.
4) 每一条出错消息都被打包在一个 HTML span 元素里,可以通过覆盖其行标为 errorMessage 的那个 css 样式来改变出错消息的格式。查看源代码可知,错误消息span标签有个class属性,其值为errorMessage。
5) 显示错误消息:如果是 simple 主题,系统默认不显示出错消息,需要通过 <s:fielderror fieldName=“filedname”></s:fielderror> 标签显示错误消息。其中,fieldname为实际的属性值。
定制类型转换器
为什么需要自定义的类型转换器 ?
答:因为 Struts 不能自动完成 字符串 到 引用类型 的 转换。
自定义类型转换器必须实现 ongl.TypeConverter 接口或对这个接口的某种具体实现做扩展
在大多数类型转换器里, 需要提供从 String 类型到非 String 类型和与此相反的转换功能
在 StrutsTypeConverter 中有两个抽象方法:
在应用程序里使用一个自定义的类型转换器之前, 必须先对它进行配置。这种配置既可以基于字段,也可以基于类型:
基于字段配置
可以为某个 Model(该 Model 类也可能是 Action) 的各个属性分别配置一个自定义的转换器
1. 创建一个属性文件: ModelClassName-conversion.properties, 该文件需和相对应的 Model 类放在同一个目录下
2. 编辑属性文件:
fieldname1=customConverter1
fieldname2=customConverter2
基于类型配置
在 WEB-INF/classes/ 目录下创建 xwork-conversion.properties 文件.
在 xwork-conversion.properties 文件里把每一个需要进行类型转换的类与一个类型转换器关联起来
fullyQualifiedClassName=CustomConverter1
自定义类型转换器小结
I. 开发类型转换器的类:一般扩展 StrutsTypeConverter 类。
II. 配置类型转换器:
有两种方式
①. 基于字段的配置:
> 在字段所在的 Model(可能是 Action, 可能是一个 JavaBean) 的包下, 新建一个 ModelClassName-conversion.properties 文件
> 在该文件中输入键值对: fieldName=类型转换器的全类名.
> 第一次使用该转换器时创建实例.
> 类型转换器是单实例的!
②. 基于类型的配置:
> 在 src 下新建 xwork-conversion.properties
> 键入: 待转换的类型=类型转换器的全类名.
> 在当前 Struts2 应用被加载时创建实例。不是单实例
示例
要求如下:
实现自定义的时间类型转换器: 时间 pattern 需要以 web 应用的初始化参数配置在 web.xml 中
<context-param> <param-name>datePattern</param-name> <param-value>yyyy/MM/dd hh:mm:ss</param-value> </context-param>
若类型转换失败,给出自定义的信息。
1. 添加JSP页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="s" uri="/struts-tags"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>struts2之类型转换示例</title> </head> <body> <h1>struts2之类型转换示例</h1> <s:form name="order" action="order-commit.action" method="POST"> <s:textfield name="id" label="订单编号"></s:textfield> <s:textfield name="name" label="订单名称"></s:textfield> <s:textfield name="price" label="订单价格"></s:textfield> <s:textfield name="createdate" label="订单日期"></s:textfield> <s:submit value="提交"></s:submit> </s:form> </body> </html>
2. 添加模型和Action类
package org.rabbitx.web.struts2.example.dateconvert.action;
import java.util.Date;
public class Order {
private int id;
private String name;
private double price;
private Date createdate;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public Date getCreatedate() {
return createdate;
}
public void setCreatedate(Date createdate) {
this.createdate = createdate;
}
}
package org.rabbitx.web.struts2.example.dateconvert.action;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
import com.opensymphony.xwork2.Preparable;
public class OrderAction extends ActionSupport implements Preparable,ModelDriven<Order>{
private static final long serialVersionUID = 7623439468123773653L;
private Order order;
public String commit()
{
System.out.println("------OrderAction----------order----------");
System.out.println("order: " + order);
return SUCCESS;
}
@Override
public void prepare() throws Exception {
order = new Order();
}
@Override
public Order getModel() {
return order;
}
}
3. 配置action
<package name="order" extends="struts-default"> <default-interceptor-ref name="paramsPrepareParamsStack"/> <action name="order-*" class="org.rabbitx.web.struts2.example.dateconvert.action.OrderAction" method="{1}"> <result name="success">/success.jsp</result> <result name="input">/exampleDateConvert/order.jsp</result> </action> </package>
4. 添加类型转换器
package org.rabbitx.web.struts2.example.dateconvert.action;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletContext;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.util.StrutsTypeConverter;
public class DateConverter extends StrutsTypeConverter{
private SimpleDateFormat dateFormat;
public DateConverter()
{
System.out.println("----DateConverter------constructor-------------");
//如果基于类型配置转换器,则不能在构造器中调用此方法生成SimpleDateFormat对象。
//因为,基于类型配置的转换器会在web应用时加载(实例被创建),而加载全局转换器时,ServletContext对象还未创建,会发生空指针异常。
//解决的办法是系统第一次调用转换方法的时候在创建SimpleDateFormat对象
//createDateFormat();
}
public void createDateFormat()
{
if(dateFormat == null)
{
ServletContext context = ServletActionContext.getServletContext();
String datePattern = context.getInitParameter("datePattern");
Locale locale = Locale.getDefault();
dateFormat = new SimpleDateFormat(datePattern,locale);
}
}
@SuppressWarnings("rawtypes")
@Override
public Object convertFromString(Map context, String[] values, Class toClass) {
try
{
if(toClass == Date.class)
{
return getDateFormat().parse(values[0]);
}
}
catch (ParseException e)
{
e.printStackTrace();
}
return values;
}
@SuppressWarnings("rawtypes")
@Override
public String convertToString(Map context, Object o) {
if(o instanceof Date)
{
return getDateFormat().format((Date)o);
}
return null;
}
public SimpleDateFormat getDateFormat() {
createDateFormat();
return dateFormat;
}
public void setDateFormat(SimpleDateFormat dateFormat) {
this.dateFormat = dateFormat;
}
}
5. 基于属性或基于类型配置类型转换器
5.1 基于属性:在与模型Order相同目录下创建Order-conversion.properties文件并添加下面内容:
createdate=org.rabbitx.web.struts2.example.dateconvert.action.DateConverter
5.2 基于类型:在src目录下创建xwork-conversion.properties文件并添加下面内容:
java.util.Date=org.rabbitx.web.struts2.example.dateconvert.action.DateConverter
类型转换与复杂属性配合使用
form 标签的 name 属性可以被映射到一个属性的属性.
类型转换与Collection配合使用
Struts 还允许填充 Collection 里的对象, 这常见于需要快速录入批量数据的场合