今天学习了Struts2的输入校验,学到了不少东西。大家都知道,在一个Web应用中,输入校验起着一个十分重要的角色,所以今天我综合Struts2.1李刚版本的书籍来探讨Strurs的输入校验。
输入校验分为客户端校验和服务器校验,客户端校验主要是过滤正常用户的误操作,主要通过JavaScript完成,服务器端校验是整个应用阻止非法数据的最后防线,主要通过在应用中编程实现。
下面我们逐一逐一介绍:
1.客户端校验(JavaScript):
Web程序如下图:
1.1在index.jsp创建一个连接通过action跳进/WEB-INF/JavaScriptValidate/regist.jsp(此处用了struts标签)
<!-- 基于JavaScript客户端完成校验 --> <s:url value="v_toJSValidate" var="jsvalidate"/> <p><a href="${jsvalidate}">基于JavaScript客户端完成校验</a></p>
1.2创建一个ValidatorsAction负责跳转
package com.and_9.actions.validators; import com.opensymphony.xwork2.ActionSupport; public class ValidatorsAction extends ActionSupport{ public String toJSValidate(){ return "toJSValidate"; } }
1.3在struts.xml添加如下内容:
<!-- 跳转各种校验 --> <action name="v_*" class="com.and_9.actions.validators.ValidatorsAction" method="{1}"> <result name="toJSValidate">/WEB-INF/JavaScriptValidate/regist.jsp</result> </action>
1.4添加一个RegistActionByJS的action类:
package com.and_9.actions.javascript_validate; import java.util.Date; import com.opensymphony.xwork2.ActionSupport; public class RegistActionByJS extends ActionSupport{ //封装用户请求参数的四个属性 private String name; private String pass; private int age; private Date birth; //name属性的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } //pass属性的setter和getter方法 public void setPass(String pass) { this.pass = pass; } public String getPass() { return this.pass; } //age属性的setter和getter方法 public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } //birth属性的setter和getter方法 public void setBirth(Date birth) { this.birth = birth; } public Date getBirth() { return this.birth; } }
1.6在WEB-INF/JavaScriptValidate/分别添加regist.jsp和show.jsp
regist.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@taglib prefix="s" uri="/struts-tags"%> <% 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>My JSP 'index.jsp' starting 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"> <script type="text/javascript"> //校验表单的JavaScript函数 function validate(form) { //定义错误字符串 var errStr = ""; //依次取出表单中的四个表单域的值 var username = trim(form.name.value); var pass = trim(form.pass.value); var age = trim(form.age.value); var birth = trim(form.birth.value); //判断用户名不能为空 if (username == "" || username == null) { errStr += "您的用户名必须输入"; } //判断用户名必须是数字和字母,且长度必须为4到25之间 else if (!/^\w{4,25}$/.test(username)) { errStr += "\n您的用户名必须是字母和数字,且长度在4到25之间"; } //判断密码必须输入 if (pass == "" || pass == null) { errStr += "\n您的密码必须输入"; } //判断密码必须是数字和字母,且长度必须为4到25之间 else if (!/^\w{4,25}$/.test(pass)) { errStr += "\n您的密码必须是字母和数字,且长度在4到25之间"; } //判断年龄必须输入 if (age == "" || age == null) { errStr += "\n您的年龄必须输入"; } //判断年龄必须是一个有效的年龄 else if (!/^[0-1]?[0-9]?[0-9]$/.test(age)) { errStr += "\n您的年龄必须为整数,且必须是一个有效的年龄值"; } //判断生日必须输入 if (birth == "" || birth == null) { errStr += "\n您的生日必须输入"; } //判断生日必须是一个有效的日期,且只能是19xx年,或者20xx年 else if(!/^19\d\d\-[0-1]\d\-[0-3]\d$/.test(birth) && !/^20[0-1]\d\-[0-1]\d\-[0-3]\d$/.test(birth)) { errStr += "\n您的生日格式不正确,格式:yyyy-MM-DD"; } //如果错误字符串为空,表明客户端校验通过 if (errStr == "") { return true; } //客户端校验没有通过,通过警告框输出校验失败提示 else { alert(errStr); return false; } } //一个简单的截去字符串前后空格的函数 function trim(s) { return s.replace("/^\s*/" , "") .replace("/\s*$/" , "") } </script> </head> <body> <!-- 基于JavaScript客户端完成校验 --> <s:form action="js_regist" method="post" onsubmit="return validate(this);"> <!-- 使用s:textfield标签生成文本输入框 --> <s:textfield label="用户名" name="name"/> <s:password label="密码" name="pass"/> <s:textfield label="年龄" name="age"/> <s:textfield label="生日" name="birth"/> <s:submit/> </s:form> </body> </html>
show.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@taglib prefix="s" uri="/struts-tags"%> <% 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>My JSP 'index.jsp' starting 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> <h3>基于JavaScript客户端校验成功</h3> 用户名:<s:property value="name"/><br /> 密码:<s:property value="pass"/><br /> 年龄:<s:property value="age"/><br /> 生日:<s:property value="birth"/><br /> <p><a href="index.jsp">返回首页</a></p> </body> </html>
1.7在struts.xml添加如下内容(校验失败则会返回input):
<!-- 基于JavaScript校验 --> <action name="js_regist" class="com.and_9.actions.javascript_validate.RegistActionByJS"> <result name="input">/WEB-INF/JavaScriptValidate/regist.jsp</result> <result>/WEB-INF/JavaScriptValidate/show.jsp</result> </action>
1.8程序效果如下:
输入数据正确
2服务器端校验(XML方式):
Web程序如下图
2.1在index.jsp创建一个连接, 通过action <!---->跳进到/WEB-INF/XMLValiudate/regist.jsp
<s:url value="v_toXMLValidate" var="xmlvalidate"/> <p><a href="${xmlvalidate}">基于XML配置完成校验</a></p>
2.2在ValidatorsAction添加如下方法,负责跳转
public String toXMLValidate(){ return "toXMLValidate"; }
2.3新增一个包为com.and_9.actions.xml_validate,在该包下创建RegistActionByXML类(其实内容同上):
package com.and_9.actions.xml_validate; import java.util.Date; import com.opensymphony.xwork2.ActionSupport; public class RegistActionByXML extends ActionSupport{ //封装用户请求参数的四个属性 private String name; private String pass; private int age; private Date birth; //name属性的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } //pass属性的setter和getter方法 public void setPass(String pass) { this.pass = pass; } public String getPass() { return this.pass; } //age属性的setter和getter方法 public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } //birth属性的setter和getter方法 public void setBirth(Date birth) { this.birth = birth; } public Date getBirth() { return this.birth; } }
2.4重点来了,配置相应的xml,来实现输入校验:
<?xml version="1.0" encoding="GBK"?> <!-- 指定校验配置文件的DTD信息 --> <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.3//EN" "http://www.opensymphony.com/xwork/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> <!-- 如果校验失败,输出name.requried对应的国际化信息 --> <message key="name.requried"/> </field-validator> <!-- 指定name属性必须匹配正则表达式 --> <field-validator type="regex"> <param name="expression"><![CDATA[(\w{4,25})]]></param> <!-- 如果校验失败,输出name.regex对应的国际化信息 --> <message key="name.regex"/> </field-validator> </field> <!-- 校验Action的pass属性 --> <field name="pass"> <!-- 指定pass属性必须满足必填规则 --> <field-validator type="requiredstring" short-circuit="true"> <param name="trim">true</param> <!-- 如果校验失败,输出pass.requried对应的国际化信息 --> <message key="pass.requried"/> </field-validator> <!-- 指定pass属性必须满足匹配指定的正则表达式 --> <field-validator type="regex"> <param name="expression"><![CDATA[(\w{4,25})]]></param> <!-- 如果校验失败,输出pass.regex对应的国际化信息 --> <message key="pass.regex"/> </field-validator> </field> <!-- 指定age属性必须在指定范围内--> <field name="age"> <field-validator type="int" > <param name="min">1</param> <param name="max">150</param> <!-- 如果校验失败,输出age.range对应的国际化信息 --> <message key="age.range"/> </field-validator> </field> <!-- 指定birth属性必须在指定范围内--> <field name="birth"> <field-validator type="date"> <!-- 下面指定日期字符串时,必须使用本Locale的日期格式 --> <param name="min">1900-01-01</param> <param name="max">2050-02-21</param> <!-- 如果校验失败,输出birth.range对应的国际化信息 --> <message key="birth.range"/> </field-validator> </field> </validators>
2.5上面用到了国际化信息此时我们需要国际化信息资源文件RegistActionByXML_zh_CN.properties,放进该action的包内,记得要转换成zh_CN(当然不清楚怎么转换可以留下评论我会补充上):
2.6在src目录下添加globalMessages_zh_CN.properties,其内容为(主要为了当类型转化失败时候输出提示):
对应struts.xml添加如下内容(全局嘛):
<constant name="struts.custom.i18n.resources" value="globalMessages"/>
2.7在/WEB-INF/XMLValiudate/regist.jsp添加如下内容:
<!-- 基于XML配置完成校验 --> <s:form action="xml_regist" method="post" > <!-- 使用s:textfield标签生成文本输入框 --> <s:textfield label="用户名" name="name"/> <s:password label="密码" name="pass"/> <s:textfield label="年龄" name="age"/> <s:textfield label="生日" name="birth"/> <s:submit/>
2.8在/WEB-INF/XMLValiudate/show.jsp添加如下内容:
<!--EndFragmen-->
<h3>基于XML配置校验成功</h3> 用户名:<s:property value="name"/><br /> 密码:<s:property value="pass"/><br /> 年龄:<s:property value="age"/><br /> 生日:<s:property value="birth"/><br /> <p><a href="index.jsp">返回首页</a></p>
2.9最后在struts.xml配置相应action就大功告成了
<!-- 基于XML配置文件校验 --> <action name="xml_regist" class="com.and_9.actions.xml_validate.RegistActionByXML"> <result name="input">/WEB-INF/XMLValiudate/regist.jsp</result> <result>/WEB-INF/XMLValiudate/show.jsp</result> </action>
2.10接下来运行程序,当我们什么都没有输入的时候点击提交:
当我们输入非法(或错误)数据时候点击提交
2.11总结
当然,上面只是用到的字段校验风格,还有一种校验风格叫非字段校验风格,是一种以校验器优先的配置方式。上面用到了short-circuit短路校验器,让系统应该仅输出第一行提示信息,而不是一次输出所有的校验提示。
对于校验器的执行顺序有如下原则(摘自李刚)
(1)所有非字段风格的校验器优先于字段风格的校验器
(2)所有非字段风格的校验其中,排在前面的会先执行
(3)所有字段风格的校验器中,排在前面的会先执行
对于校验器短路的原则是(摘自李刚):
(4)所有非字段校验器是最优先执行,如果某个非字段校验器校验失败了,则该字段上所有字段校验器都不会获得校验的机会。
(5)非字段校验器的校验失败,不会阻止其他非字段校验的执行。
(6)如果一个字段校验器校验失败后,则该字段下的且排在该校验失败的校验器之后的其它字段校验器不会获得校验机会。
(7)字段校验器永远都不会阻止非字段校验器的执行。
假设系统中有两个Action:BaseAction和和RegistAction,则系统搜寻规则文件顺序如下:
(1)BaseAction-validation.xml
(2)BaseAction-别名-validation.xml
(3)RegistAction-validation.xml
(4)RegistAction-别名-validation.xml
不管是否找到配置文件,系统总是按固定顺序搜索。
如果在RegistAction中包含一个login方法,在struts.xml配置调用的action方式name为login,则校验规则是BaseAction-validation.xml、BaseAction-login-validation.xml、RegistAction-validation.xml、RegistAction-login-validation.xml。如果两个校验文件中指定的校验规则冲突,则后面的文件中的校验规则取胜。
3手动完成输入校验
3.1虽然Struts2校验器的校验可以完成绝大部分的输入校验,但是现实生活中,对于一些特殊的校验要求,可能需要在Struts2中进行手动校验。
3.2我们知道Struts2的Action类里可以包含多个处理逻辑,不同处理逻辑对应不同的方法。如果我们的输入校验只是想校验某个处理器逻辑,则我们可以通过重写validateXxx()方法。Xxx即是Action对应的处理逻辑方法。
Web程序图如下:
3.3首先,我们先创建一个com.adn9.actions.xxx_validate包,在包内添加RegistActionByXxx类
package com.adn9.actions.xxx_validate; import com.opensymphony.xwork2.ActionSupport; public class RegistActionByXxx extends ActionSupport{ //封装用户请求参数的四个属性 private String name; private String pass; //name属性的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } //pass属性的setter和getter方法 public void setPass(String pass) { this.pass = pass; } public String getPass() { return this.pass; } public String xxx_regist(){ return SUCCESS; } public void validateXxx_regist() { // TODO Auto-generated method stub if(name.length()>0&&name.equals(pass)){ addFieldError("name","用户名和密码不能相同"); } } }
public void validateXxx_regist() {
// TODO Auto-generated method stub
if(name.length()>0&&name.equals(pass)){
addFieldError("name","用户名和密码不能相同");
}
}
就是我们所手动重写的校验方法。
3.4在/WEB-INF/XxxValidate/分别添加regist.jsp和show.jsp
regist.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@taglib prefix="s" uri="/struts-tags"%> <% 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>My JSP 'index.jsp' starting 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"> </head> <body> <!-- 基于XML配置完成校验 --> <s:form action="xxx_regist" method="post" > <!-- 使用s:textfield标签生成文本输入框 --> <s:textfield label="用户名" name="name"/> <s:password label="密码" name="pass"/> <s:submit/> </s:form> </body> </html>
show.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@taglib prefix="s" uri="/struts-tags"%> <% 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>My JSP 'index.jsp' starting 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> <h3>基于Xxx校验成功</h3> 用户名:<s:property value="name"/><br /> 密码:<s:property value="pass"/><br /> <p><a href="index.jsp">返回首页</a></p> </body> </html>
3.4在struts.xml添加如下内容:
<action name="xxx_regist" class="com.adn9.actions.xxx_validate.RegistActionByXxx" method="xxx_regist"> <result name="input">/WEB-INF/XxxValidate/regist.jsp</result> <result>/WEB-INF/XxxValidate/show.jsp</result> </action>
3.5程序效果:
接下来我们来看程序效果
如果什么都不输入,直接提交(因为我添加了xml配置校验):
如果用户名和密码都输入了aaaa:
如果用户名和密码分别输入aaaa和bbbb:
由此看出xml配置校验会与validate一起校验,xml配置优先。此外,如果Action内添加了validate方法,则也会起作用,而且validateRegist优先调用。
4Annotation的输入校验
4.1Annation的输入校验事迹上是Sturts2的“零配置”特性部分,它允许使用Annation来定义每个字段应该满足的规则。
Web程序图如下
4.2首先,创建com.and_9.actions.annotation_validate包,然后创建RegistActionByAnnotation类:
package com.and_9.actions.annotation_validate; 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; import com.opensymphony.xwork2.validator.annotations.Validations; import com.opensymphony.xwork2.validator.annotations.ValidatorType; //使用@Validation修饰Action类 @Validations public class RegistActionByAnnotation extends ActionSupport { private String name; private String pass; private int age; private Date birth; //使用Annotation指定必填、正则表达式两个校验规则 @RequiredStringValidator(type = ValidatorType.FIELD, key = "name.requried",message = "") @RegexFieldValidator(type = ValidatorType.FIELD, expression = "\\w{4,25}",key = "name.regex",message = "") public void setName(String name) { this.name = name; } public String getName() { return (this.name); } //使用Annotation指定必填、正则表达式两个校验规则 @RequiredStringValidator(type = ValidatorType.FIELD, key = "pass.requried",message = "") @RegexFieldValidator(type = ValidatorType.FIELD, expression = "\\w{4,25}",key = "pass.regex",message = "") public void setPass(String pass) { this.pass = pass; } public String getPass() { return (this.pass); } //使用Annotation指定整数范围校验规则 @IntRangeFieldValidator(message = "", key = "age.range", min = "1", max = "150") public void setAge(int age) { this.age = age; } public int getAge() { return (this.age); } //使用Annotation指定日期范围校验规则 @DateRangeFieldValidator(message = "", key = "birth.range", min = "1900/01/01", max = "2050/01/21") public void setBirth(Date birth) { this.birth = birth; } public Date getBirth() { return (this.birth); } }
上面正是用了Annotation来定义每个字段应该满足规则
4.3然后在/WEB-INF/AnnotationValidate/下分别创建regist.jsp和show.jsp
rejist.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@taglib prefix="s" uri="/struts-tags"%> <% 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>My JSP 'index.jsp' starting 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"> </head> <body> <!-- 基于Annotation配置完成校验 --> <s:form action="ann_regist" method="post" > <!-- 使用s:textfield标签生成文本输入框 --> <s:textfield label="用户名" name="name"/> <s:password label="密码" name="pass"/> <s:textfield label="年龄" name="age"/> <s:textfield label="生日" name="birth"/> <s:submit/> </s:form> </body> </html>
show.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@taglib prefix="s" uri="/struts-tags"%> <% 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>My JSP 'index.jsp' starting 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> <h3>基于Annotation配置校验成功</h3> 用户名:<s:property value="name"/><br /> 密码:<s:property value="pass"/><br /> 年龄:<s:property value="age"/><br /> 生日:<s:property value="birth"/><br /> <p><a href="index.jsp">返回首页</a></p> </body> </html>
4.4然后在struts.xml配置如下:
<!-- 基于Annotation方法 --> <action name="ann_regist" class="com.and_9.actions.annotation_validate.RegistActionByAnnotation"> <result name="input">/WEB-INF/AnnotationValidate/regist.jsp</result> <result>/WEB-INF/AnnotationValidate/show.jsp</result> </action>
4.5程序效果图:
什么都不输入,直接点击提交:
数据格式输入正确:
以上是个人基于李刚版本Struts2书籍所总结的全文。当然,存在一些不足的地方希望大家可以多多指导。