Struts2 输入校验

        Struts2的输入校验分为服务器校验和客户端校验,其中服务器校验比较简单,而客户端的校验约束较多,一般是二者的结合使用。相对于服务器校验,Struts2首先执行的是客户端校验,此时对于输入的错误Struts2并不进行提交,从而减轻了服务器负担。

一、编写校验规则

    校验规则分为字段校验规则(字段优先)和非字段校验器规则(校验器优先).对于前者的格式:

        <field name="被校验字段名">

                <field-validator type="校验器名">

                        <param name="参数名">参数值</param>

                          ....多个param省略

                        <message key="资源文件键名">[可选提示信息]</message>

                </field-validator>

        </field>

    对于后者格式:

    <validator type="校验器名">

            <param name="fieldName">需要被检验的字段名</param>

            <param name="参数名">参数值</param>

            ...多个param省略        

            <message key="资源文件键名">[可选提示信息]</message>

     </validator>

    (注意区分大小写,而且如果字段名或者校验器名称写错,则该校验文件失效或者报错)


二、服务器校验

     欢迎页面(registForm.jsp)

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    
    <title>Register Page</title>
    
	<meta http-equiv="pragma" content="no-cache">
	<meta http-equiv="cache-control" content="no-cache">
	<meta http-equiv="expires" content="0">    
	<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
	<meta http-equiv="description" content="This is my page">
	<!--
	<link rel="stylesheet" type="text/css" href="styles.css">
	-->
  </head>
  
  <body>
	<s:head/>
    <s:form action="loginPro">
    	<s:textfield name="name" label="用户名"/>
    	<s:textfield name="pass" label="密码"/>
    	<s:textfield name="age" label="年龄"/>
    	<s:textfield name="birth" label="生日"/>
    	<s:submit value="登陆"/>
    </s:form>
  </body>
</html>

控制类(RegistAction.java):

package DeepUse;

import java.util.Date;

import com.opensymphony.xwork2.ActionSupport;

public class RegistAction extends ActionSupport{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private String name;
	private String pass ;
	private int age;
	private Date birth ;
	
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String getPass() {
		return pass;
	}
	
	public void setPass(String pass) {
		this.pass = pass;
	}
	
	public int getAge() {
		return age;
	}
	
	public void setAge(int age) {
		this.age = age;
	}
	
	public Date getBirth() {
		return birth;
	}

	public void setBirth(Date birth) {
		this.birth = birth;
	}

	public String login() {
		return SUCCESS;
	}
}

校验规则(RegistAction-validation.xml)

<?xml version="1.0" encoding="UTF-8"?>
<!-- 指定校验配置文件的DTD信息 -->
<!DOCTYPE validators PUBLIC
	"-//Apache Struts//XWork Validator 1.0.3//EN"
	"http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">
<!-- 校验文件的根元素 -->
<validators>
	<!-- 校验Action的name属性 -->
	<field name="name">
		<!-- 指定name属性为必填 -->
		<field-validator type="requiredstring">
			<param name="trim">true</param> <!-- 这里不知道也可以,因为默认是去掉字符串的前后空格-->
			<message>必须输入名字</message><!-- 注意这里可以用国际资源代替,比如写为<message key="name.required"/> -->
                                            <!-- 此时需要写一个局部或者全局的国家资源文件RegistAction.properties,内容为name.required=您输入的用户名只能数字或者字母,且长度在4到25之间(注意用native2ascii转化)见4.1内容 -->
		</field-validator>
		<!-- 指定name属性必须是正则表达式 -->
		<field-validator type="regex">
			<param name="expression"><![CDATA[(\w{4,25})]]></param><!-- 注意这里是express,不是书上的regex(注解也是这样)-->
			<message>您输入的用户名只能是字母和数字,且长度必须在4到25之间</message>
		</field-validator>
	</field>
	
	<!-- 校验Action的pass属性 -->
	<field name="pass">
		<!-- 指定pass属性为必填 -->
		<field-validator type="requiredstring" short-circuit="true">
			<param name="trim">true</param>
			<message>必须输入密码</message>
		</field-validator>
		<!-- 指定pass属性必须是正则表达式 -->
		<field-validator type="regex">
			<param name="expression"><![CDATA[(\w{4,25})]]></param>
			<message>您输入的密码是字母和数字,且长度必须在4到25之间</message>
		</field-validator>
	</field>
	
	<!-- 校验Action的age属性 -->
	<field name="age">
		<!-- 指定age属性为指定范围内 -->
		<field-validator type="int">
			<param name="min">1</param>
			<param name="max">150</param>
			<message>年纪必须在1到150之间</message>
		</field-validator>
	</field>
	
	<!-- 校验Action的birth属性 -->
	<field name="birth">
		<!-- 指定age属性为指定范围内 -->
		<field-validator type="date">
			<param name="min">1900-01-01</param>
			<param name="max">2050-01-01</param>
			<message>生日必须在${min}到${max}之间</message>
		</field-validator>
	</field>
</validators>

配置文件(struts.xml)

<action name="*Pro" class="DeepUse.RegistAction" method="{1}"> <!-- 使用login方法来处理 -->
		<result name="success">/Struts2BaseUse/success.jsp</result>
		<result name="input">/Struts2DeepUse/registForm.jsp</result> <!-- 校验失败和之前转化类似,失败返回到input页面 -->
	</action>

注意将RegistAction-validation.xml放在和RegistAction.class同一个目录下(在WEB-IN/classes/<包名>/ 目录下)

结果:



三、客户端校验

    要求:输入页面的表单元素改为Struts2的标签来生成

            在<s:form ...>加入validate=true属性

    如果和上次一样访问该jsp会出现下面FreeMarker template error错误,其中FreeMarker是默认模板,其余模板需要开发

     

这是因为在该jsp文件显示之前要先读取其中的validation文件,而这是要先走Struts2框架流程,即要先Filter. 也就是从之前的.jsp->.action->.jsp转为 .action->.jsp/.action->.jsp过程。简而言之,就是将jsp放入WEB-INF里面,然后通过action映射到jsp上。网上有的说通过在web.xml中构造servlet映射,具体如下:

 <servlet>
 	<servlet-name>Regist</servlet-name>
 	<jsp-file>/WEB-INF/my/registForm.jsp</jsp-file> <!-- 注意这里的文件结构 WEB-INF下面有个my目录,在my目录存放我们的jsp文件 -->
 </servlet>
 
 <servlet-mapping>
 	<servlet-name>Regist</servlet-name>
 	<url-pattern>/registForm</url-pattern>
 </servlet-mapping>

这样我们在地址栏上面输入http://localhost:8080/Struts2/registForm就能访问到WEB-INF里面的jsp,其中Struts2是当前工程的名称,依据自己工程的不同而做相应替换.这样真的可以访问到? 当然不能,因为当输入这个地址的时候,会被Struts2的Action的所拦截,也就是会被Struts2当成一个action,然后在struts.xml中查找,一般找不到会报action异常. 如何修改呢?很简单, 在registForm加上任意后缀,比如.jsp甚至是.html来将自己页面封装成简单网页.

当我们这么做了,会解决上面的问题? 显然是不能,因为我们还是首先访问的jsp,只不过我们可以访问WEB-INF里面的jsp了,算是一个小成就。那么如何先action再jsp呢。这个也不难,只要在struts.xml中加上如下几句即可:

<action name="*">
	<result>/WEB-INF/my/{1}.jsp</result><!-- 这里将任何没有匹配的action,进行映射到WEB-INF/my/*.jsp中 -->
</action>

和第二段中的struts.xml合并一下. 这样当我们在地址栏中输入http://localhost:8080/Struts2/registForm,会被当作是访问一个action,当该action匹配不到struts2.xml中的action的时候,会进行*通配,来映射到/WEB-INF/my/registForm.jsp,当然事先我们早已将该jsp放入该目录了,因此实现了先action再jsp的过程,这样就顺利解决了上面的问题.

对上面的欢迎页面进行修改后(registForm.jsp):

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    
    <title>Register Page</title>
    
	<meta http-equiv="pragma" content="no-cache">
	<meta http-equiv="cache-control" content="no-cache">
	<meta http-equiv="expires" content="0">    
	<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
	<meta http-equiv="description" content="This is my page">
	<!--
	<link rel="stylesheet" type="text/css" href="styles.css">
	-->
  </head>
  
  <body>
	<s:head/>
    <s:form action="loginPro" validate="true">
    	<s:textfield name="name" label="用户名"/>
    	<s:textfield name="pass" label="密码"/>
    	<s:textfield name="age" label="年龄"/>
    	<s:textfield name="birth" label="生日"/>
    	<s:submit value="登陆"/>
    </s:form>
  </body>
</html>

怎么出现了key属性而不是key属性对于局部资源文件的值呢,这是由于对于客户端校验必须使用全局资源文件。

其次对于客户端的校验,发现当点击登录的时候,地址栏还是和原来一样,也就是说表单经客户端验证,并没有进行提交。


四、短路校验

    一般而言,当直接提交的时候,会进行很多的验证,Struts2会将所有验证出错的信息均显示出来。从而影响友好性,因此可以用短路校验来解决该问题,当第一次校验失败的时候,会停止该字段的继续校验,实现也很简单,就是在验证文件对于的校验器中加上short-circuit="true",这样当对该字段的该校验器进行校验后,如果没通过,则不会继续对该字段校验了.

    欢迎页面(regist.jsp)和第三段中的一样、控制类(RegistAction.java)和第二段中的一样、配置文件(struts.xml)和第三段中的一样、校验文件(RegistAction-validation.xml)如下:

<?xml version="1.0" encoding="UTF-8"?>
<!-- 指定校验配置文件的DTD信息 -->
<!DOCTYPE validators PUBLIC
	"-//Apache Struts//XWork Validator 1.0.3//EN"
	"http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">
<!-- 校验文件的根元素 -->
<validators>
	<!-- 校验Action的name属性 -->
	<field name="name">
		<!-- 指定name属性为必填 -->
		<field-validator type="requiredstring" short-circuit="true"><!-- 注意这里的短路校验器的关键 -->
			<param name="trim">true</param>
			<message>必须输入名字</message>
		</field-validator>
		<!-- 指定name属性必须是正则表达式 -->
		<field-validator type="regex"> <!-- 如果上面的短路了则这里不会校验 -->
			<param name="expression"><![CDATA[(\w{4,25})]]></param>
			<message>您输入的用户名只能是字母和数字,且长度必须在4到25之间</message>
		</field-validator>
	</field>
	
	<!-- 校验Action的pass属性 -->
	<field name="pass">
		<!-- 指定pass属性为必填 -->
		<field-validator type="requiredstring" short-circuit="true">
			<param name="trim">true</param>
			<message>必须输入密码</message>
		</field-validator>
		<!-- 指定pass属性必须是正则表达式 -->
		<field-validator type="regex">
			<param name="expression"><![CDATA[(\w{4,25})]]></param>
			<message>您输入的密码是字母和数字,且长度必须在4到25之间</message>
		</field-validator>
	</field>
	
	<!-- 校验Action的age属性 -->
	<field name="age">
		<!-- 指定age属性为指定范围内 -->
		<field-validator type="int">
			<param name="min">1</param>
			<param name="max">150</param>
			<message>年纪必须在1到150之间</message>
		</field-validator>
	</field>
	
	<!-- 校验Action的birth属性 -->
	<field name="birth">
		<!-- 指定age属性为指定范围内 -->
		<field-validator type="date">
			<param name="min">1900-01-01</param>
			<param name="max">2050-01-01</param>
			<message>生日必须在${min}到${max}之间</message>
		</field-validator>
	</field>
</validators>

这里直接点击登录,现在只显示一条信息,而不是很多


五、校验文件搜索规则、原则

    Action别名的校验器,对于不同的处理逻辑,校验规则文件是不能分清的。也就是对于login和regist的两个action,是不同用一个RegistAction-validation.xml来区分校验的。Struts2提供的lRegistAtion-别名-validation.xml来分别处理,这里的别人是action的名称,比如上面的login和regist。这样当服务端接收到对于的别名action,会先调用RegistAction-别名-validation.xml,然后再调用RegistAction-validation.xml. 如果RegistAction还继承了其他类比如BaseAction,那么还会调用BaseAction-validation.xml以及BaseAction-别名-validation.xml.

    搜索文件总是从上往下,一直搜索,后面的文件如果有冲突会替换前面的.

    (上面的在实验中无法读取RegistAction-别名-validation.xml文件,也就是该文件不起作用)***

    执行顺序:前面的优先执行,非字段校验器优先级大于字段校验器

    短路原则:非字段校验器某个字段校验失败,则该字段后面的校验器停止,当时其他字段正常进行

                    字段校验某个字段校验失败,则停止了

                    字段校验器无论如何不会影响到非字段校验器的执行


六、内建校验器

    required 和requiredstring二者都是非空校验器,前者属性无,后者只是针对字符串.后者有个属性trim

    int long short 属性都含有min 和max

    date 属性含有min和max

    expression 属性就是expression,可以是正则表达式或基于ValueStack进行求值

    fieldexpression 与上面一个相比,这个是和字段相关的表达式

    email 属性无

    url 属性无

    visitor 这是很强的一个校验器,针对一个对象来进行校验

    conversion 转化校验器(暂时不明白)***

    stringlength 字符串长度校验器,属性minLength和maxLength

    regex 用expression代替否则不可用正则表达式


欢迎页面(registForm.jsp):

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    
    <title>Register Page</title>
    
	<meta http-equiv="pragma" content="no-cache">
	<meta http-equiv="cache-control" content="no-cache">
	<meta http-equiv="expires" content="0">    
	<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
	<meta http-equiv="description" content="This is my page">
	<!--
	<link rel="stylesheet" type="text/css" href="styles.css">
	-->
  </head>
  
  <body>
	<s:head/>
    <s:form action="registPro" validate="true">
    	<s:textfield name="user.name" label="用户名"/> <!-- 注意这里用ONGL来给user对象里面属性赋值 -->
    	<s:textfield name="user.pass" label="密码"/>
    	<s:textfield name="user.age" label="年龄"/>
    	<s:textfield name="user.birth" label="生日"/>
    	<s:textfield name="urltest" label="url"/> <!-- 这是测试url -->
    	<s:textfield name="mailtest" label="mail"/><!-- 这是测试email -->
        <s:textfield name="lentest" label="len"/><!-- 这是测试字符串长度 -->
    	<s:submit value="注册"/>
    </s:form>
  </body>
</html>

JavaBean (User.java):

package DeepUse;

import java.util.Date;

public class User {
	private String name;
	private String pass ;
	private int age;
	private Date birth;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPass() {
		return pass;
	}
	public void setPass(String pass) {
		this.pass = pass;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public Date getBirth() {
		return birth;
	}
	public void setBirth(Date birth) {
		this.birth = birth;
	}
}

配置文件(struts.xml)和第三段中的一样

控制类(RegistAction.java):

package DeepUse;

import java.util.Date;

import com.opensymphony.xwork2.ActionSupport;

public class RegistAction extends ActionSupport{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	private User user;

	public User getUser() {
		return user;
	}

	public void setUser(User user) {
		this.user = user;
	}
	
	public String regist() { //因为struts.xml中要求用该方法处理
		return SUCCESS;
	}
}

校验文件(RegistAction-validation.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!-- 指定校验配置文件的DTD信息 -->
<!DOCTYPE validators PUBLIC
	"-//Apache Struts//XWork Validator 1.0.3//EN"
	"http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">
<!-- 校验文件的根元素 -->
<validators>
	<!-- 校验Action的name属性 -->
	<field name="user">
		<!-- 指定name属性为必填 -->
		<field-validator type="visitor">
			<param name="context">userContext</param><!-- 这里指定了userContext,因此需要写一个User-userContext-validation.xml -->
			<param name="appendPrefix">true</param><!-- 这个表示要在输出的FieldError前面加上前缀,前缀是啥?是下面的message
			<message>用户的: </message>
		</field-validator>
	</field>
	<field name="urltest">
		<field-validator type="url">
			<message>输入的url不合规范</message>
		</field-validator>
	</field>
	<field name="mailtest">
		<field-validator type="email">
			<message>输入的email不合规范</message>
		</field-validator>
	</field>
	<field name="lentest">
		<field-validator type="stringlength">
			<param name="minLength">4</param>
			<param name="maxLength">20</param>
			<message>该段字符串要求长度在4到20之间</message>
		</field-validator>
	</field>
</validators>

因为用到userContext,因此写一个User-userContext-validation.xml文件,该文件是放在User.class类目录下,而不是上面的RegistAction.class目录下,但在测试中两个目录是同一个目录,因此在你的classes目录下应该有User.class ,RegistAction.class,RegistAction-validation.xml,User-userContext-validation.xml四个文件

结果:




六、注解校验和实现validate自定义校验

    这种校验很方便,直接在控制类中加入注解来实现校验。从而避免了配置各种文件的繁琐,但是这有个弊端就是所有内容写入Java代码中,使得后期维护困难.

配置文件(struts.xml)和第三段中的一样.

控制类(RegistAction.java):

package DeepUse;

import java.util.Date;

import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.validator.annotations.DateRangeFieldValidator;
import com.opensymphony.xwork2.validator.annotations.IntRangeFieldValidator;
import com.opensymphony.xwork2.validator.annotations.RegexFieldValidator;
import com.opensymphony.xwork2.validator.annotations.RequiredStringValidator;

public class RegistActionBaseAnnotation extends ActionSupport{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	private String name;
	private String pass ;
	private int age;
	private Date birth;
	@RequiredStringValidator(key="name.required",message="")
	@RegexFieldValidator(expression = "\\w{4,25}",key="name.regex",message="")
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@RequiredStringValidator(key="pass.required",message="")
	@RegexFieldValidator(expression = "\\w{4,25}",key="pass.regex",message="")//注意这里用expression而不是regex 
	public String getPass() {
		return pass;
	}
	public void setPass(String pass) {
		this.pass = pass;
	}
	@IntRangeFieldValidator(message="",key="age.range",min="1",max="150")
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@DateRangeFieldValidator(message="",key="birth.range",min="1900/01/01",max="2050/01/01")
	public Date getBirth() {
		return birth;
	}
	public void setBirth(Date birth) {
		this.birth = birth;
	}
	public String regist() {
		return SUCCESS;
	}
	@Override
	public void validate() { //这个校验对所有逻辑均进行 
		// TODO Auto-generated method stub
		System.out.println("进入validate方进行校验"+name);
		if(!name.contains("abc")){
			addFieldError("user", "您的用户名必须包含abc");// 当用addFieldError的时候,此时的该加入的错误信息
                                                    //不会被struts2表单所显示,从而要使用s:fielderror显示
		}
	}
	public void validateRegist() {// 这个校验对于逻辑为regist的action会起作用,而且会优先validate先运行
		System.out.println("进入validateRegist()方法校验"+name);
		if(!name.contains(".org")){
			addFieldError("user", "您的用户名必须包含.org");
		}
	}
}

结果:


上面就实现了注解的校验,因为注解中的资源文件需要全局的,对于局部的则显示了键名


上面是重写了validate自定义校验,看出validateXxx比validate优先级高,Xxx为处理逻辑的方法名,比如上面就是用regist方法实现的.但是两者都会执行。

和前一篇结合看,Struts2首先进行转化检验然后再进行输入检验.

猜你喜欢

转载自blog.csdn.net/whitenigt/article/details/80159155