首页 > 代码库 > JAVAWEB开发之Struts2详解(二)——Action接受请求参数、类型转换器、使用Struts2的输入校验、以及遵守约定规则实现Struts2的零配置

JAVAWEB开发之Struts2详解(二)——Action接受请求参数、类型转换器、使用Struts2的输入校验、以及遵守约定规则实现Struts2的零配置

Action接受请求参数

作为MVC框架,必须要负责解析HTTP请求参数,并将其封装到Model对象中
Struts2提供了非常强大的类型转换机制用于请求数据 到 model对象的封装
Struts2和MVC定义关系
StrutsPrepareAndExecuteFilter:控制器
在Struts2中action是什么?(Struts2是一个MVC框架)
V:jsp
M:action
C:action StrutsPrepareAndExecuteFilter

Struts2提供了三种数据封装方式:
  • Action本身作为model对象,通过成员setter封装。(属于属性驱动)
  • 创建独立model对象,页面通过ognl表达式封装。(属于属性驱动)
  • 使用ModelDrivern接口,对请求数据进行封装。(属于模型驱动)
具体使用如下:

1.属性驱动

 属性驱动方式(一):直接将action作为一个model,就可以得到请求参数
action类成员变量setter接收参数如下图所示:
技术分享
问题1:action封装请求参数,会不会存在线程安全问题?
   是不会的,因为每一次请求,都是一个新的action
优点:使用简单
缺点:需要单独定义JavaBean,将action中属性copy到JavaBean中(不能将Action作为model传给service层)
这种方式,底层是通过反射进行实现的。

属性驱动方式(二):创建一个单独的Model类,在action类中引用model作为成员变量。(页面使用ognl)
具体操作:
在action类中声明一个mdoel,private User user; 
提供对应的setter和getter方法。
在页面上使用ognl来进行描述
<input type="text" name="user.username"> 如下图所示:
技术分享
这种方式的优点:简单易使用,解决了第一种封装的问题。
缺点:在页面上使用了ognl表达式,页面不通用了。
问题:这种方式,数据是怎样封装的?
是通过Struts2中name为params的interceptor拦截器进行的数据封装(Struts的核心core包下struts-default.xml中定义)<interceptor name="params" class="com.opensymphony.xwork2.interceptor.ParametersInterceptor"/>

2.模型驱动(在开发中应用比较多)

模型驱动的步骤:
1.让action 类实现ModelDrivern接口。
2.重写getModel方法
3.在action中实例化一个model对象,让getModel方法返回这个对象。
  public class Login3Action extends ActionSupport implements ModelDriven<User> {
private User user = new User();
public User getModel() {
return user;
}
  }
如下图所示:
技术分享
优点:解决了属性驱动存在的问题
缺点:一次只能封装一个model对象
Struts2有很多围绕模型驱动的属性(在struts-default.xml中的拦截器中定义)
<interceptor name="modelDriven" class="com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor"/>
扩展:
(1)将数据封装到List集合。类型转换与Collection配合使用。
 Struts2还允许填充Collection里的对象,这常见于需要快速录入批量数据的场合。
技术分享
页面:
 username1:<input type="text" name="users[0].username"><br>
 password1:<input type="password" name="users[0].password"><br>
 username2:<input type="text" name="users[1].username"><br>
 password2:<input type="password" name="users[1].password"><br>

action类:
 private List<User> users;
 get/set方法
(2)将数据封装到Map集合(类型转换与Map配合使用)
技术分享
页面:
username1:<input type="text" name="map[‘aaa‘].username"><br>
password1:<input type="password" name="map[‘aaa‘].password"><br>
username2:<input type="text" name="map[‘bbb‘].username"><br>
password2:<input type="password" name="map[‘bbb‘].password"><br>

action类:
private Map<String, User> map;
提供get/set
示例:
1.将action作为model(属性驱动方式一)
Login1Action
package cn.itcast.action;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionSupport;

// 获取请求参数  属性驱动 第一种,直接将action作为model
public class Login1Action extends ActionSupport {
	private String username;
	private String password;

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	@Override
	public String execute() throws Exception {
		HttpServletRequest request = ServletActionContext.getRequest();
		// 2.判断用户名与密码是否正确
		if ("tom".equals(username) && "123".equals(password)) {

			request.getSession().setAttribute("username", username);
			return SUCCESS;

		} else {
			request.setAttribute("login.message", "用户名或密码错误");
			return "failer";
		}

	}
}
login1.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!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>Insert title here</title>
</head>
<body>
	${requestScope["login.message"]}
	<br>
	<form action="${pageContext.request.contextPath}/login1" method="POST">
		username:<input type="text" name="username"><br>
		password:<input type="password" name="password"><br>
		<input type="submit" value=http://www.mamicode.com/"登录">>
2.将model对象作为Action类成员(属性驱动模式二)
封装Model类
User
package cn.itcast.domain;

public class User {
	private String username;
	private String password;

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	@Override
	public String toString() {
		return "User [username=" + username + ", password=" + password + "]";
	}

}

Login2Action
package cn.itcast.action;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts2.ServletActionContext;

import cn.itcast.domain.User;

import com.opensymphony.xwork2.ActionSupport;

// 获取请求参数  属性驱动 第二种,直接在action上声明一个model
public class Login2Action extends ActionSupport {
	private User user;

	public User getUser() {
		return user;
	}

	public void setUser(User user) {
		this.user = user;
	}

	@Override
	public String execute() throws Exception {
		HttpServletRequest request = ServletActionContext.getRequest();
		// 2.判断用户名与密码是否正确
		if ("tom".equals(user.getUsername())
				&& "123".equals(user.getPassword())) {

			request.getSession().setAttribute("username", user.getUsername());
			return SUCCESS;

		} else {
			request.setAttribute("login.message", "用户名或密码错误");
			return "failer";
		}

	}
}
login2.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!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>Insert title here</title>
</head>
<body>
	${requestScope["login.message"]}
	<br>
	<form action="${pageContext.request.contextPath}/login2" method="POST">
		username:<input type="text" name="user.username"><br>
		password:<input type="password" name="user.password"><br>
		<input type="submit" value=http://www.mamicode.com/"登录">>
3.模型驱动(action类实现ModelDrivern接口)
Login3Action
package cn.itcast.action;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts2.ServletActionContext;

import cn.itcast.domain.User;

import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;

// 获取请求参数 模型驱动
public class Login3Action extends ActionSupport implements ModelDriven<User> {
	private User user=new User();
	
	@Override
	public User getModel() {
		return user;
	}
	
	@Override
	public String execute() throws Exception {
		HttpServletRequest request = ServletActionContext.getRequest();
		// 2.判断用户名与密码是否正确
		if ("tom".equals(user.getUsername())
				&& "123".equals(user.getPassword())) {

			request.getSession().setAttribute("username", user.getUsername());
			return SUCCESS;

		} else {
			request.setAttribute("login.message", "用户名或密码错误");
			return "failer";
		}

	}

}
login3.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!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>Insert title here</title>
</head>
<body>
	${requestScope["login.message"]}
	<br>
	<form action="${pageContext.request.contextPath}/login3" method="POST">
		username:<input type="text" name="username"><br>
		password:<input type="password" name="password"><br>
		<input type="submit" value=http://www.mamicode.com/"登录">>

success.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!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>Insert title here</title>
</head>
<body>
	${username }
</body>
</html>

4.将数据封装到List集合
list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!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>Insert title here</title>
</head>
<body>

	<form action="${pageContext.request.contextPath}/list" method="POST">
		username:<input type="text" name="users[0].username"><br>
		password:<input type="password" name="users[0].password"><br>
		username:<input type="text" name="users[1].username"><br>
		password:<input type="password" name="users[1].password"><br>
		<input type="submit" value=http://www.mamicode.com/"登录">>
ListAction
package cn.itcast.action;

import java.util.List;

import cn.itcast.domain.User;

import com.opensymphony.xwork2.ActionSupport;

public class ListAction extends ActionSupport {

	private List<User> users;

	public List<User> getUsers() {
		return users;
	}

	public void setUsers(List<User> users) {
		this.users = users;
	}

	@Override
	public String execute() throws Exception {
		System.out.println(users);
		return null;
	}
}
5.将数据封装到Map集合
map.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!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>Insert title here</title>
</head>
<body>

	<form action="${pageContext.request.contextPath}/map" method="POST">
		username:<input type="text" name="map[‘aaa‘].username"><br>
		password:<input type="password" name="map[‘aaa‘].password"><br>
		username:<input type="text" name="map[‘bbb‘].username"><br>
		password:<input type="password" name="map[‘bbb‘].password"><br>
		<input type="submit" value=http://www.mamicode.com/"登录">>
MapAction
package cn.itcast.action;

import java.util.Map;

import cn.itcast.domain.User;

import com.opensymphony.xwork2.ActionSupport;

public class MapAction extends ActionSupport {
	private Map<String, User> map;

	public Map<String, User> getMap() {
		return map;
	}

	public void setMap(Map<String, User> map) {
		this.map = map;
	}

	@Override
	public String execute() throws Exception {
		System.out.println(map);
		return null;
	}
}
配置struts.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
	"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
	"http://struts.apache.org/dtds/struts-2.3.dtd">

<struts>

	<constant name="struts.enable.DynamicMethodInvocation" value=http://www.mamicode.com/"false" />>

验证属性驱动方式一:
技术分享  技术分享
验证属性驱动方式二:
技术分享技术分享

验证模型驱动:
技术分享技术分享
验证封装数据到List集合
技术分享技术分享
验证封装数据到Map集合
技术分享
技术分享

2自定义类深入剖析属性驱动模式一和模型驱动

首先导入DOM4J、XPATH、BeanUtils的相关jar包
技术分享
在src下新建struts.xml
新建自定义的ModelDrivern接口MyModelDrivern
package cn.itcast;

public interface MyModelDriven<T> {

	public T getModel();
}
创建User类(和上面的一样)
......
创建Action类
package cn.itcast.action;

import cn.itcast.MyModelDriven;
import cn.itcast.User;

public class HelloAction implements MyModelDriven<User> {
   // 剖析模型驱动
	private User user = new User();

	public User getModel() {
		return user;
	}

	/*  属性驱动模式一
	 private String username;
	 private String password;
	
	 public String getUsername() {
	 return username;
	 }
	
	 public void setUsername(String username) {
	 this.username = username;
	 }
	
	 public String getPassword() {
	 return password;
	 }

	 public void setPassword(String password) {
	 this.password = password;
	 }
     */

	public String say() {
		System.out.println(user.getUsername() + "  " + user.getPassword());
		return "good";
	}
}
自定义拦截器(模仿Struts中的params拦截器)
StrutsFilter
package cn.itcast.filter;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.beanutils.BeanUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import cn.itcast.MyModelDriven;

public class StrutsFilter implements Filter {

	public void init(FilterConfig filterConfig) throws ServletException {

	}

	public void doFilter(ServletRequest req, ServletResponse resp,
			FilterChain chain) throws IOException, ServletException {

		// 1.强转
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) resp;

		// 2.操作

		// 2.1 得到请求资源路径
		String uri = request.getRequestURI();
		String contextPath = request.getContextPath();
		String path = uri.substring(contextPath.length() + 1);

		// System.out.println(path); // hello

		// 2.2 使用path去struts.xml文件中查找某一个<action name=path>这个标签
		SAXReader reader = new SAXReader();

		try {
			// 得到struts.xml文件的document对象。
			Document document = reader.read(new File(this.getClass()
					.getResource("/struts.xml").getPath()));

			Element actionElement = (Element) document
					.selectSingleNode("//action[@name=‘" + path + "‘]"); // 查找<action
																			// name=‘hello‘>这样的标签

			if (actionElement != null) {
				// 得到<action>标签上的class属性以及method属性
				String className = actionElement.attributeValue("class"); // 得到了action类的名称
				String methodName = actionElement.attributeValue("method");// 得到action类中的方法名称。

				// 2.3通过反射,得到Class对象,得到Method对象
				Class actionClass = Class.forName(className);
				Method method = actionClass.getDeclaredMethod(methodName);

				// 处理请求参数封装:

				Object actionObj = actionClass.newInstance();

				// 2.模型驱动
				if (actionObj instanceof MyModelDriven) {
					MyModelDriven mmd = (MyModelDriven) actionObj;

					BeanUtils.populate(mmd.getModel(),
							request.getParameterMap());
				} else {
					// 1.属性驱动
					BeanUtils.populate(actionObj, request.getParameterMap());//
				}

				// 2.4 让method执行.
				String returnValue = http://www.mamicode.com/(String) method.invoke(actionObj); // 是让action类中的方法执行,并获取方法的返回值。>
编辑自定义的struts.xml
<?xml version="1.0" encoding="UTF-8" ?>
<struts>

	<action name="hello" class="cn.itcast.action.HelloAction"
		method="say">
		<result name="good">/hello.jsp</result>
	</action>
</struts>
在web.xml中配置拦截器
创建jsp进行测试:
hello.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    
    <title>My JSP ‘index.jsp‘ starting page</title>
  </head>
  
  <body>
	<h1>hello Struts2</h1>
  </body>
</html>
index.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    
    <title>My JSP ‘index.jsp‘ starting page</title>
	
  </head>
  
  <body>
  	<a href=http://www.mamicode.com/"${pageContext.request.contextPath}/hello?username=tome&password=123">第一次使用struts2>

测试结果如下:
技术分享技术分享

Struts2内置的类型转换器

类型转换:即在web中 使用Servlet时,使用BeanUtils直接将表单数据封装到JavaBean中。
对于大部分常用类型,开发者根本无需创建自己的转换器,Struts2内置了常见数据类型的多种转换器。
  • boolean和Boolean
  • char和Character
  • int和Integer
  • long和Long
  • float和Float
  • double和Double
  • Date可以接收yyyy-MM-dd格式字符串(浏览器语言首选项为中文时)
  • 数组  可以将多个同名参数转换到数组中
  • 集合   支持将数据保存到List或者Map集合
例如:日期类型,我们传递yyyy-MM-dd yyyy年MM月dd日 格式都可以,但是如果是yyyy/MM/dd就会出现问题。
关于Struts2中的类型转换器:
Struts2中的类型转换器根接口是:com.opensymphony.xwork.conversion.TypeConverter

自定义类型转换器

Struts2提供常规类型转换器,可以实现常用类型的转换,但是如果目标类型是一个特殊类型,则需要自定义转换器
Struts2 类型转换器实际上都是基于OGNL实现的,在OGNL项目中,有一个TypeConverter接口
自定义类型转换器必须实现 ongl.TypeConverter接口
技术分享
相关的关键源代码如下:
TypeConverter接口关键源代码:
技术分享

DefaultTypeConverter类实现了TypeConverter接口:

技术分享

StrutsTypeConverter直接继承自DefaultTypeConverter:
技术分享
自定义转换器的的步骤:
   1.创建一个类实现TypeConverter接口
   2.重写接口中方法,实现类型转换操作
   3.注册类型转换器
 
详细说明:
1.创建一个自定义类型转换器(有三种方式)
方式一:实现TypeConverter需要重写
   public Object convertValue(Map<String, Object> context, Object target, Member member, String propertyName,        Object value, Class toType);
   如果实现接口,这个方法参数太多(6个)
方式二:不推荐实现接口,可以继承DefaultTypeConverter类
   优点:重写的方法参数没有那么多
    public Object convertValue(Map<String, Object> context, Object value, Class toType) {
return convertValue(value, toType);
    }
方式三:(推荐使用)继承DefaultTypeConverter类的一个子类StrutsTypeConverter   
   public abstract Object convertFromString(Map context, String[] values, Class toClass);——请求封装
   public abstract String convertToString(Map context, Object o);——数据回显
   类型转换器一直都是双向转换:
   页面提交的请求参数,封装到model——需要转换
   model数据  需要在页面上回显——需要转换
2.怎样注册一个自定义类型转换器
  注册方式有三种类型:
方式一:局部—针对于action
  配置文件所在的位置及名称:在Action类所在包下,创建 Action类名-conversion.properties
  配置文件书写:格式  属性名称=类型转换器的全类名
方式二:局部—针对于Model(属性驱动模式二)
  配置文件所在位置以及名称:在Model类所在包,创建 Model类名-conversion.properties
  配置文件书写:格式  属性名称=类型转换器的全类名
方式三:全局
  配置文件所在位置以及名称:在src下创建一个 xwork-conversion.properties
  配置文件书写  格式:要转换的类型全名=类型转换器的全类名

注意:
对于struts2中类型转换器,如果表单数据提交时,将数据向model封装,出现了问题,会报错:
No result defined for action cn.itcast.action.RegistAction and result input
上面的意思是说,在RegistAction的配置中没有配置input结果视图.
<action name="regist" class="cn.itcast.action.RegistAction">
<result name="input">/success.jsp</result>
</action>
如果配置了,出现类型转换问题,就会跳转到input指定的视图。

问题:为什么会向input视图跳转?

是因为struts2中的拦截器(interceptor).
在struts2中的conversionError拦截器用于记录类型转换问题
 <interceptor name="conversionError" class="org.apache.struts2.interceptor.StrutsConversionErrorInterceptor"/>
在struts2中的workflow拦截器用于得到问题,向input视图跳转。
<interceptor name="workflow" class="com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor"/>

类型转换中的错误处理:
  • Struts2提供了一个名为conversionError的拦截器,查看struts-default.xml
  • <interceptor name="conversionError" class="org.apache.struts2.interceptor.StrutsConversionErrorInterceptor"/>
  • 如果Struts2的类型转换器执行类型转换时出现错误,该拦截器将负责将对应错误封装成表单域错误(FieldError),并将这些错误信息放入ActionContext中
  • 使用类型转换中的错误处理用户定义Action必须继承ActionSupport
  • 在自定义类型转换器中,异常必须抛出不能捕获,conversionError会处理该异常,然后转入名为input的逻辑视图
  • 在Action所在包中,创建 ActionName.properties,在局部资源文件中配置提示信息 :invalid.fieldvalue.属性名= 错误信息
  • 在input逻辑视图所对应jsp页面中,通过<s:fielderror/> 输出类型转换信息
程序示例如下:
技术分享   技术分享

首先新建自定义转换类MyDateConverter (通过继承自DefaultTypeConverter)
package cn.itcast.utils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

import ognl.DefaultTypeConverter;

public class MyDateConverter extends DefaultTypeConverter {
	@Override
	public Object convertValue(Map context, Object value, Class toType) {
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");

		try {
			if (toType == Date.class) {// 当字符串向Date类型转换时
				String[] params = (String[]) value;
				return dateFormat.parse(params[0]);
			} else if (toType == String.class) {// 当Date转换成字符串时
				Date date = (Date) value;
				return dateFormat.format(date);
			}
		} catch (ParseException e) {
			throw new RuntimeException("日期转换错误");
		}
		return null;
	}
}
再新建一个日期转换类(使用另外一种方式继承自StrutsTypeConverter)
MyDateConverter2
package cn.itcast.utils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

import org.apache.struts2.util.StrutsTypeConverter;

public class MyDateConverter2 extends StrutsTypeConverter {

	// 接收页面传递的数据封装到JavaBean
	@Override
	public Object convertFromString(Map context, String[] values, Class toClass) {

		String value = http://www.mamicode.com/values[0];>
具体代码如下:
RegistAction
package cn.itcast.action1;

import cn.itcast.domain.User;

import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;

public class RegistAction extends ActionSupport implements ModelDriven<User> {
	private User user = new User();

	@Override
	public User getModel() {
		return user;
	}

	@Override
	public String execute() throws Exception {
       System.out.println(user);
		return null;
	}

}
技术分享
Regist2Action
package cn.itcast.action2;

import java.util.Arrays;
import java.util.Date;

import com.opensymphony.xwork2.ActionSupport;

public class Regist2Action extends ActionSupport {
	private String username;
	private String password;
	private int age;
	private Date birthday;
	private String[] hobby;
	
	
	@Override
	public String toString() {
		return "Regist2Action [username=" + username + ", password=" + password
				+ ", age=" + age + ", birthday=" + birthday + ", hobby="
				+ Arrays.toString(hobby) + "]";
	}


	public String getUsername() {
		return username;
	}


	public void setUsername(String username) {
		this.username = username;
	}


	public String getPassword() {
		return password;
	}


	public void setPassword(String password) {
		this.password = password;
	}


	public int getAge() {
		return age;
	}


	public void setAge(int age) {
		this.age = age;
	}


	public Date getBirthday() {
		return birthday;
	}


	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}


	public String[] getHobby() {
		return hobby;
	}


	public void setHobby(String[] hobby) {
		this.hobby = hobby;
	}


	@Override
	public String execute() throws Exception {
		System.out.println(this);
		return null;
	}
}

Regist2Action-conversion.properties
birthday=cn.itcast.utils.MyDateConverter2
User
package cn.itcast.domain;

import java.util.Arrays;
import java.util.Date;

public class User {
	private String username;
	private String password;
	private int age;
	private Date birthday;
	private String[] hobby;

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public Date getBirthday() {
		return birthday;
	}

	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}

	public String[] getHobby() {
		return hobby;
	}

	public void setHobby(String[] hobby) {
		this.hobby = hobby;
	}

	@Override
	public String toString() {
		return "User [username=" + username + ", password=" + password
				+ ", age=" + age + ", birthday=" + birthday + ", hobby="
				+ Arrays.toString(hobby) + "]";
	}

}
User-conversion.properties
birthday=cn.itcast.utils.MyDateConverter

struts.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
	"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
	"http://struts.apache.org/dtds/struts-2.3.dtd">

<struts>

	<package name="default" namespace="/" extends="struts-default">
		<action name="regist" class="cn.itcast.action1.RegistAction">
			<result name="input">/success.jsp</result>
		</action>
		<action name="regist2" class="cn.itcast.action2.Regist2Action">
			<result name="input">/success.jsp</result>
		</action>
	</package>

</struts>

regist.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!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>Insert title here</title>
</head>
<body>

	<form action="${pageContext.request.contextPath}/regist" method="POST">
   username:<input type="text" name="username"><br>
   password:<input type="password" name="password"><br>
   hobby:<input type="checkbox" name="hobby" value=http://www.mamicode.com/"eat">吃>
regist2.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!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>Insert title here</title>
</head>
<body>

	<form action="${pageContext.request.contextPath}/regist2" method="POST">
   username:<input type="text" name="username"><br>
   password:<input type="password" name="password"><br>
   hobby:<input type="checkbox" name="hobby" value=http://www.mamicode.com/"eat">吃>
success.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>
<title>Insert title here</title>
</head>
<body>
  <s:fielderror/>
</body>
</html>
技术分享技术分享
技术分享
技术分享

Struts2的输入校验

校验的分类 : 客户端数据校验 和 服务器端数据校验 
客户端数据校验 ,通过JavaScript 完成校验 (改善用户体验,使用户减少出错 )
服务器数据校验 ,使用框架内置校验功能 (struts2 内置校验功能 ) ----- 必须的

struts2 支持校验方式 
代码校验 :在服务器端通过编写java代码,完成数据校验 
配置校验 :XML配置校验(主流) 和 注解配置校验

代码校验(手动校验)了解

要求:action类必须继承自ActionSupport。需要重写一个validate方法
通过测试发现在action中的validate方法执行了。并且是在请求处理方法(execute)之前执行的。
对于struts2提供的校验,它也是通过拦截器实现的。

问题1:在validate方法中怎样存储错误校验信息?
  在validate方法中  this.addFieldError(String name,String value);
问题2:在页面上怎样获取错误信息?(在Input视图上)
 <s:fielderror> 展示所有的错误信息
<s:fielderror fieldName=""> 展示特定名称的错误信息
问题3:在同一个Action中有多个请求处理方法(login,regist) ,那么有些方法是需要校验的,有些是不需要的,如何处理?
解决方案:创建一个名称叫 validate+请求方法处理名(首字母大写)  例如:请求处理方法叫  regist()  校验 的方法名 validateRegist().会发现不管定义多少validateXxx方法 执行时都会走validate方法,所以可以将一些公共的校验放在validate方法中,可以把针对于特定方法的检验放在validateXxx方法下(xxx请求方法处理名)

手动校验的流程如下:
第一步:类型转换器对请求参数执行类型转换,并将转换后的值赋给action中的属性。
第二步:如果在执行转换的过程中出现异常,系统会将异常信息保存到ActionContext,conversionError拦截器将异常信息封装到fieldErrors里,然后执行第三步。如果类型转换没有出现异常,则直接进入第三步。
第三步:系统通过反射技术调用action中的ValidateXxx()方法,xxx为请求方法处理名。
第四步:调用action中的validate方法。
第五步:经过上面4步,如果系统中的fieldErrors存在错误信息(即存放错误信息的集合的size大于0),系统自动将请求转发至名称为input的视图。如果系统中的fieldErrors没有任何错误信息,系统将执行action中的处理方法。
手动校验示例如下:
导入Strutsjar包 配置好struts.xml
在WebRoot下新建login.jsp和regist.jsp
login.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib prefix="s" uri="/struts-tags"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>

<title>My JSP ‘index.jsp‘ starting page</title>

</head>

<body>
	<s:fielderror/>
	<form action="${pageContext.request.contextPath}/login" method="post">
		username:<input type="text" name="username"><s:fielderror fieldName="username.message"/><br>
		password:<input type="password" name="password"><s:fielderror fieldName="password.message"/><br>
		repassword:<input type="password" name="repassword"><br>
		
		hobby:<input type="checkbox" name="hobby" value=http://www.mamicode.com/"eat">吃

regist.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib prefix="s" uri="/struts-tags"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>

<title>My JSP ‘index.jsp‘ starting page</title>

</head>

<body>
	<s:fielderror/>
	<form action="${pageContext.request.contextPath}/regist" method="post">
		username:<input type="text" name="username"><s:fielderror fieldName="username.message"/><br>
		password:<input type="password" name="password"><s:fielderror fieldName="password.message"/><br>
		repassword:<input type="password" name="repassword"><br>
		
		hobby:<input type="checkbox" name="hobby" value=http://www.mamicode.com/"eat">吃
新建model类User
package cn.itcast.domain;

import java.util.Arrays;
import java.util.Date;

public class User {

	private String username;
	private String password;
	private int age;
	private Date birthday;
	private String[] hobby;

	private String url;
	private String email;
	private String telphone;

	private String repassword;

	public String getRepassword() {
		return repassword;
	}

	public void setRepassword(String repassword) {
		this.repassword = repassword;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getTelphone() {
		return telphone;
	}

	public void setTelphone(String telphone) {
		this.telphone = telphone;
	}

	public String[] getHobby() {
		return hobby;
	}

	public void setHobby(String[] hobby) {
		this.hobby = hobby;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public Date getBirthday() {
		return birthday;
	}

	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}

	@Override
	public String toString() {
		return "User [username=" + username + ", password=" + password
				+ ", age=" + age + ", birthday=" + birthday + ", hobby="
				+ Arrays.toString(hobby) + "]";
	}

}

接着使用手动校验创建Action类UserAction
package cn.itcast.action;

import cn.itcast.domain.User;

import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;

// 采用手动的方式
public class UserAction extends ActionSupport implements ModelDriven<User> {
	private User user = new User();

	@Override
	public User getModel() {
		return user;
	}

	@Override
	public String execute() throws Exception {
		System.out.println(user);
		return null;
	}

	public String login() throws Exception {
		System.out.println("login method ......");
		return null;
	}

	public String regist() throws Exception {
		System.out.println("regist method ......");
		return null;
	}

	@Override
	public void validate() {
		System.out.println("validate method ......");

	}

	public void validateRegist() {
		if (user.getUsername() == null
				|| user.getUsername().trim().length() == 0) {
			// 说明用户名为空
			this.addFieldError("username.message", "用户名不能为空");
		}

		if (user.getPassword() == null
				|| user.getPassword().trim().length() == 0) {
			this.addFieldError("password.message", "密码不能为空");
		}

		if (!(user.getAge() >= 10 && user.getAge() <= 40)) {
			this.addFieldError("age.message", "年龄必须在10-40之间");
		}

		System.out.println("validateRegist......");
	}

}

配置struts.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
	"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
	"http://struts.apache.org/dtds/struts-2.3.dtd">

<struts>

	<package name="default" namespace="/" extends="struts-default">
		<action name="regist" class="cn.itcast.action.UserAction"
			method="regist">
			<result name="input">/regist.jsp</result>
		</action>
		<action name="login" class="cn.itcast.action.UserAction"
			method="login">
			<result name="input">/login.jsp</result>
		</action>
	</package>

</struts>
输入http://localhost/mystruts2_day02_3/login.jsp 直接提交
技术分享技术分享
发现首先调用了validate方法载调用了login请求处理方法

接着调试注册方法(提供了注册校验)
本地调试 http://localhost/mystruts2_day02_3/regist.jsp  直接提交
技术分享      技术分享
发现首先调用了validateRegist方法 再调用了validate方法
因此 只要重写了validate方法,不管怎样都会执行校验。所以可以把公共部分抽取到validate方法内。

配置校验

Struts2的校验框架已经完成了校验操作(做了很多校验法)而我们在使用时只需要调用它们即可(通过配置文件)
基于XML配置方式实现输入校验:
要求:Action类必须继承自ActionSupport或实现了Validateable接口。
          在struts.xml中添加type为input的视图结果集。
           添加配置文件。
关于配置文件的配置方式:
配置文件位置: xml文件要与Action类在同一包下。
配置文件名称:Action简单类名-validation.xml
配置文件约束:xwork-core-2.3.7.jar 中 xwork-validator-1.0.3.dtd 下
<!DOCTYPE validators PUBLIC
"-//Apache Struts//XWork Validator 1.0.3//EN"
"http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">

配置文件的书写:
1.根元素:<validators>
2.子元素:<field name="属性名称"></field>
            指定action中要校验的属性,name属性指定将被验证的表单字段的名称。
3.<field>的子元素
   <field-validator  type="校验器">  是用于指定校验器
   问题:校验器有哪些?
   xwork-core-2.3.7.jar 中 /com/opensymphony/xwork2/validator/validators/default.xml下
<validator name="required" class="com.opensymphony.xwork2.validator.validators.RequiredFieldValidator"/>
<validator name="requiredstring" class="com.opensymphony.xwork2.validator.validators.RequiredStringValidator"/>
<validator name="int" class="com.opensymphony.xwork2.validator.validators.IntRangeFieldValidator"/>
<validator name="long" class="com.opensymphony.xwork2.validator.validators.LongRangeFieldValidator"/>
<validator name="short" class="com.opensymphony.xwork2.validator.validators.ShortRangeFieldValidator"/>
<validator name="double" class="com.opensymphony.xwork2.validator.validators.DoubleRangeFieldValidator"/>
<validator name="date" class="com.opensymphony.xwork2.validator.validators.DateRangeFieldValidator"/>
<validator name="expression" class="com.opensymphony.xwork2.validator.validators.ExpressionValidator"/>
<validator name="fieldexpression" class="com.opensymphony.xwork2.validator.validators.FieldExpressionValidator"/>
<validator name="email" class="com.opensymphony.xwork2.validator.validators.EmailValidator"/>
<validator name="url" class="com.opensymphony.xwork2.validator.validators.URLValidator"/>
<validator name="visitor" class="com.opensymphony.xwork2.validator.validators.VisitorFieldValidator"/>
<validator name="conversion" class="com.opensymphony.xwork2.validator.validators.ConversionErrorFieldValidator"/>
<validator name="stringlength" class="com.opensymphony.xwork2.validator.validators.StringLengthFieldValidator"/>
<validator name="regex" class="com.opensymphony.xwork2.validator.validators.RegexFieldValidator"/>
<validator name="conditionalvisitor" class="com.opensymphony.xwork2.validator.validators.ConditionalVisitorFieldValidator"/>

4.<field-validator>的子元素
    <message>错误信息</message>
    如果需要国际化,可以为message指定key属性,key的值为属性文件中的key。
5.<field-validator>的子元素
    <param name="">值</param>
     用于指定校验器中的参数
关于配置校验中的校验器:
  • required (必填校验器,要求被校验的属性值不能为null)
  • requiredstring (必填字符串校验器,要求被校验的属性值不能为null,并且长度大于0,默认情况下会对字符串去前后空格)
  • stringlength (字符串长度校验器,要求被校验的属性值必须在指定的范围内,否则校验失败,minLength参数指定最小长度,maxLength参数指定最大长度,trim参数指定校验field之前是否去除字符串前后的空格)
  •  regex (正则表达式校验器,检查被校验的属性值是否匹配一个正则表达式,expression参数指定正则表达式,caseSensitive参数指定进行正则表达式匹配时,是否区分大小写,默认值为true)
  •  int(整数校验器,要求field的整数值必须在指定范围内,min指定最小值,max指定最大值)
  •  double(双精度浮点数校验器,要求field的双精度浮点数必须在指定范围内,min指定最小值,max指定最大值)
  •  fieldexpression (字段OGNL表达式校验器,要求field满足一个ognl表达式,expression参数指定ognl表达式,该逻辑表达式基于ValueStack进行求值,返回true时校验通过,否则不通过)
  •  email(邮件地址校验器,要求如果被校验的属性值非空,则必须是合法的邮件地址)
  • url(网址校验器,要求如果被校验的属性值非空,则必须是合法的url地址)
  • date(日期校验器,要求field的日期值必须在指定范围内,min指定最小值,max指定最大值)
注意:关于param的name属性 要到指定校验器对应的类中找到属性的setter方法setXxx 其xxx就是要设置的name属性

问题配置校验,怎样处理在同一个action中存在多个请求处理方法校验问题?
只需要将校验xml文件名称修改即可:
Action简单类名-valication.xml 现在要对action类西红某一个方法校验,
就改为:Action简单类名-action名称-validation.xml
将上面手动配置校验的代码进行修改。
屏蔽UserAction中的所有校验的方法
在同一包下新建UserAction-validation.xml
<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE validators PUBLIC
  		"-//Apache Struts//XWork Validator 1.0.3//EN"
  		"http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">
<validators>
	<!-- 对username属性进行校验 -->
	<field name="username">
		<!-- 指定username不能为空 -->
		<field-validator type="requiredstring">
			<!-- 错误信息 -->
			<message>用户名不能为空---</message>
		</field-validator>
		<!-- 长度校验,规定用户名必须在6-10位之间 -->
		<field-validator type="stringlength">
			<param name="maxLength">10</param>
			<param name="minLength">6</param>
			<message>用户名必须在${minLength}-${maxLength}位之间</message>
		</field-validator>
	</field>

	<!-- 对age进行校验,规定年龄必须在10-40之间 -->
	<field name="age">
		<field-validator type="int">
			<param name="min">10</param>
			<param name="max">40</param>
			<message>年龄必须在${min}--${max}之间</message>
		</field-validator>
	</field>

	<!-- 对birthday进行校验 -->
	<field name="birthday">
		<field-validator type="date">
			<param name="min">1974-01-01</param>
			<param name="max">2004-12-31</param>
			<message>生日必须在${min}--${max}年之间</message>
		</field-validator>
	</field>

	<!-- 校验邮箱 -->
	<field name="email">
		<field-validator type="email">
			<message>邮箱格式不正确</message>
		</field-validator>
	</field>

	<!-- url校验 -->
	<field name="url">
		<field-validator type="url">
			<message>url不合法,应类似于http://www.baidu.com</message>
		</field-validator>
	</field>

	<!-- 使用正则 -->
	<field name="telphone">
		<field-validator type="regex">
			<param name="regexExpression"><![CDATA[^135[0-9]{8}$]]></param>
			<message>电话号码必须是135xxxxxxxx</message>
		</field-validator>
	</field>

	<field name="repassword">
		<field-validator type="fieldexpression">
			<param name="expression"><![CDATA[(password==repassword)]]></param>
			<message>两次密码输入不一致</message>
		</field-validator>
	</field>

</validators>	
调试结果如下:
技术分享 技术分享 
登录/login,会有同样的校验
如果要只对regist的action方法进行校验,需要做如下修改
UserAction-validation.xml修改为UserAction-regist-validation.xml即可
修改后调试如下:
技术分享技术分享

技术分享
发现login方法没有做校验,而regist方法做了校验
当然 也可以自定义校验器,不过很少自定义校验器

基于XML校验的一些特点:

当为某个action提供了ActionClassName-validation.xml和ActionClassName-ActionName-validation.xml两种规则的校验文件时,系统按下面顺序寻找校验文件:
1。AconClassName-validation.xml
2。ActionClassName-ActionName-validation.xml
系统寻找到第一个校验文件时还会继续搜索后面的校验文件,当搜索到所有校验文件时,会把校验文件里的所有校验规则汇总,然后全部应用于处理方法的校验。如果两个校验文件中指定的校验规则冲突,则只使用后面文件中的校验规则。

当action继承了另一个action,父类action的校验文件会先被搜索到。假设UserAction继承BaseAction, UserAction在struts.xml的配置如下:
<action name="user" class="cn.itcast.action.UserAction" method="{1}">
.....
</action>
访问上面名为user的action,系统先搜索到BaseAction-validation.xml, BaseAction-user-validation.xml,接着搜索到UserAction-validation.xml, UserAction-user-validation.xml。校验规则是这四个文件的总和。

编写校验文件时,不能出现提示(帮助信息)解决方案:
在编写ActionClassName-validation.xml校验文件时,如果出现不了帮助信息,可以按下面方式解决:
windwos->preferences->myeclipse->files and editors->xml->xmlcatalog
点“add”,在出现的窗口中的location中选“File system”,然后在xwork-2.1.2解压目录的src\java目录中选择xwork-validator-1.0.3.dtd,回到设置窗口的时候不要急着关闭窗口,应把窗口中的Key Type改为URI 。Key改为http://www.opensymphony.com/xwork/xwork-validator-1.0.3.dtd

约定访问提供Struts2的零配置

从struts2.1开始,struts2 引入了Convention插件来支持零配置
使用约定无需struts.xml或者Annotation配置
需要 struts2-convention-plugin-2.3.7.jar 、asm-*.jar(三个)
插件会自动搜索action、actions、struts、struts2包下所有Java类
所有实现了com.opensymphony.xwork2.Action的Java类
所有类名以Action结尾的Java类
下面类名都符合Convention插件
cn.itcast.struts2.HelloAction
cn.itcast.actions.books.BookSearchAction
cn.itcast.struts.user.UserAction
cn.itcast.estore.action.test.LoginAction

struts2-convention-plugin-2.3.7.jar 中struts-plugin.xml重要常量
<constant name="struts.convention.package.locators" value=http://www.mamicode.com/"action,actions,struts,struts2"/> 默认扫描包
<constant name="struts.convention.exclude.packages" value=http://www.mamicode.com/"org.apache.struts.*,org.apache.struts2.*,org.springframework.web.struts.*,org.springframework.web.struts2.*,org.hibernate.*"/> 不扫描
<constant name="struts.convention.action.suffix" value=http://www.mamicode.com/"Action"/> 默认扫描以Action结尾的类
<constant name="struts.convention.result.path" value=http://www.mamicode.com/"/WEB-INF/content/"/> 结果result页面存放位置
<constant name="struts.convention.classes.reload" value=http://www.mamicode.com/"false" /> Action类文件重新自动加载

如果Action类名包含Action后缀,将Action后缀去掉
将Action类名的驼峰写法,转成中划线写法
例如:
cn.itcast.struts2.HelloAction 映射到 /hello.action
cn.itcast.actions.books.BookSearchAction  映射到 /books/book-search.action
cn.itcast.struts.user.UserAction 映射到 /user/user.action
cn.itcast.estore.action.test.LoginAction 映射到 /test/login.action

默认情况下,Convention总会到Web应用的WEB-INF/content路径下定位结果资源
<constant name="struts.convention.result.path" value=http://www.mamicode.com/"/WEB-INF/content/"/>
约定: actionName + resultCode + suffix 
例如:
访问cn.itcast.struts.user.UserAction返回success
Convention优先使用 WEB-INF/content/user/user-success.jsp
如果user-success.jsp不存在,会使用user-success.html
如果user-success.html不存在,会使用user.jsp


JAVAWEB开发之Struts2详解(二)——Action接受请求参数、类型转换器、使用Struts2的输入校验、以及遵守约定规则实现Struts2的零配置