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首先进行转化检验然后再进行输入检验.