1. Struts2 的验证
1). 验证分为两种:
> 声明式验证*
>> 对哪个 Action 或者 Model 的那个字段进行验证
>> 使用什么验证规则
>> 如果验证失败, 转向哪一个页面, 显示什么错误消息
> 编程式验证
2). 声明验证的 helloworld
I. 先明确对哪一个 Action 的哪一个字段进行验证: (age)
II. 编写配置文件:
> 把 struts-2.5.12appsstruts2-showcaseWEB-INF 下的 validation.xml 文件复制到当前 Action所在的包下.(Struts2 2.5版本)
> 因为学习资料都是2.3 版本的所以这里需要将validation.xml 文件的头部
<!DOCTYPE form-validation PUBLIC
"-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.3.0//EN"
"http://jakarta.apache.org/commons/dtds/validator_1_3_0.dtd">
修改为
<!DOCTYPE validators PUBLIC
"-//Apache Struts//XWork Validator 1.0.2//EN"
"http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd">
导入对应的 dtd 文件就可以了.(这样与大多数教学视频版本一致, 在配置时也会有对应的提示 , 对于自学者来说轻松许多).
> 把该配置文件名改为 : ActionClassName-validation.xml .
> 编写验证规则: 参见 struts-2.5.12/docs/docs/int-validator.html 文档即可.
> 在配置文件中看定制错误消息:
<field name="age"> <field-validator type="int"> <param name="min">20</param> <param name="max">50</param> <message>Age needs to be between ${min} and ${max}</message> </field-validator> </field>
> 该错误消息可以国际化吗 ? 可以:
>> 在配置文件中加入 key: <message key="error.int"></message>
>> 在国际化资源文件中加入键值对 : error.int = Age needs to be between ${min} and ${max}
III. 若验证失败, 则转向 input 的 那个result, 所以需要配置 name = input 的 result
<result name="input">/validation.jsp</result>
IV. 如何显示错误消息呢?
> 若使用的 非 simple 主题, 则自动显示错误消息.
> 若使用的是 simple 主题, 则需要 s:fielderror 标签或者 EL 标签来手动进行显示.
<s:filederror name="age"></s:filederror>
OR
${filedErrors.age[0] }
3). 注意: 若一个 Action 类可以应答多个action 请求使用不同的验证规则, 怎么办?
> 为每一个不同的 action 请求定义其对应的验证文件: ActionClassName-AliasName-validation.xml
> 不带别名的配置文件: ActionClassName-validation.xml 中的验证规则依然会发发生作用. (可以把 action 共用的验证规则配置在里面)
4). 声明式验证框架的原理:
> Struts2 默认的拦截器栈中提供了一个 validation 拦截器
> 每一个具体的验证规则对应具体一个验证器. 参看 com.opensymphony.xwork2.validator.validators 下的 default.xml 发现它把验证器与验证规则相关联
<validator name="int" class="com.opensymphony.xwork2.validator.validators.IntRangeFieldValidator"/>
5). 短路验证: 若对一个字段使用多个验证器, 默认情况下会执行所有的验证. 若希望前面的验证器没有通过, 后面的就不在验证, 看可以使用短路验证.
<!-- 设置短路验证: 若当前验证没用通过, 则不再进行下面的验证 --> <field-validator type="conversion" short-circuit="true"> <message>Conversion Error Occurred</message> </field-validator> <field-validator type="int"> <param name="min">20</param> <param name="max">50</param> <message key="error.int"></message> </field-validator>
6). 若类型转换失败, 默认情况下 还会执行后面的拦截器, 还会进行验证. 可以通过修改 ConversionErrorInterceptor 源代码的方式(新建同样的包,及文件到达覆盖的效果,切记源文件不要随便修改都是复制出来修改.)
当类型转换失败时, 不在执行后续的验证拦截器, 而是直接返回 input 的result
Object action = invocation.getAction(); if (action instanceof ValidationAware) { ValidationAware va = (ValidationAware) action; //如果已经有错误发生,就不再向后面执行而是直接返回到input页面. if(va.hasFieldErrors() || va.hasActionErrors()){ return "input"; } }
7). 关于非字段验证: 不是针对某一个字段的验证
<validator type="expression">
<param name="expression"><![CDATA[password==password2]]></param>
<message>两次密码不一致!</message>
</validator>
显示非字段验证的错误消息, 使用 s:actionerror 标签: <s:actionerror/>
8). 不同的字段使用同样的验证规则, 而且使用同样的响应消息.
可以在国际化资源文件中:
error.int = ${getText(fieldName)} needs to be between ${min} and ${max}
age=Age
count=Count
9). 自定义验证器
I. 定义一个验证器的类
> 自定义验证器都需要实现 Validator.
> 可以选择继承 ValidatorSupport 或 FieldValidatorSupport 类
> 若希望实现一个一般的验证器, 则可以继承 ValidatorSupport
> 若希望实现一个字段验证器, 则可以继承 FieldValidatorSupport
> 具体实现可以参考目前已经有的验证器.
> 若验证程序需要接受一个输入参数, 需要为这个参数增加一个对应的属性.
II. 在配置文件中配置验证器
> 默认情况下, Struts2 会在类路径的根目录下加载 validators.xml 文件 . 在该文件中加载验证器.
该文件的定义方式同默认的验证器的那个配置文件: com.opensymphony.xwork2.validator.validators 下的 default.xml
> 若类路径下没有指定的验证器, 则从 com.opensymphony.xwork2.validator.validators 下的 default.xml 中的验证器加载.
III. 使用 : 和目前的验证器一样
IV. 示例代码: 自定义一个 身份证验证器
1). 表单输入页面 input.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="/struts-tags" prefix="s"%> <!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> <s:actionerror/> <s:form action="testValidation"> <s:textfield name="idCard" label="idCard"></s:textfield> <s:submit></s:submit> </s:form> </body> </html>
2). 一个身份证验证类CheckIdCard(网上有许多不需要自己写, 只要能看懂会用就行)
1 package com.struts.validation.app; 2 3 import java.text.SimpleDateFormat; 4 import java.util.Date; 5 /** 6 * 验证身份证号码 身份证号码, 可以解析身份证号码的各个字段,以及验证身份证号码是否有效; 身份证号码构成:6位地址编码+8位生日+3位顺序码+1位校验码 7 * 8 */ 9 public class CheckIdCard { 10 private String cardNumber; // 完整的身份证号码 11 private Boolean cacheValidateResult = null; // 缓存身份证是否有效,因为验证有效性使用频繁且计算复杂 12 private Date cacheBirthDate = null; // 缓存出生日期,因为出生日期使用频繁且计算复杂 13 private final static String BIRTH_DATE_FORMAT = "yyyyMMdd"; // 身份证号码中的出生日期的格式 14 private final static Date MINIMAL_BIRTH_DATE = new Date(-2209017600000L); // 身份证的最小出生日期,1900年1月1日 15 private final static int NEW_CARD_NUMBER_LENGTH = 18; 16 private final static int OLD_CARD_NUMBER_LENGTH = 15; 17 private final static char[] VERIFY_CODE = { '1', '0', 'X', '9', '8', '7', 18 '6', '5', '4', '3', '2' }; // 18位身份证中最后一位校验码 19 private final static int[] VERIFY_CODE_WEIGHT = { 7, 9, 10, 5, 8, 4, 2, 1, 20 6, 3, 7, 9, 10, 5, 8, 4, 2 };// 18位身份证中,各个数字的生成校验码时的权值 21 public boolean validate() { 22 if (null == cacheValidateResult) { 23 boolean result = true; 24 result = result && (null != cardNumber); // 身份证号不能为空 25 result = result && NEW_CARD_NUMBER_LENGTH == cardNumber.length(); // 身份证号长度是18(新证) 26 // 身份证号的前17位必须是阿拉伯数字 27 for (int i = 0; result && i < NEW_CARD_NUMBER_LENGTH - 1; i++) { 28 char ch = cardNumber.charAt(i); 29 result = result && ch >= '0' && ch <= '9'; 30 } 31 // 身份证号的第18位校验正确 32 result = result 33 && (calculateVerifyCode(cardNumber) == cardNumber 34 .charAt(NEW_CARD_NUMBER_LENGTH - 1)); 35 // 出生日期不能晚于当前时间,并且不能早于1900年 36 try { 37 Date birthDate = this.getBirthDate(); 38 result = result && null != birthDate; 39 result = result && birthDate.before(new Date()); 40 result = result && birthDate.after(MINIMAL_BIRTH_DATE); 41 /** 42 * 出生日期中的年、月、日必须正确,比如月份范围是[1,12],日期范围是[1,31],还需要校验闰年、大月、小月的情况时, 43 * 月份和日期相符合 44 */ 45 String birthdayPart = this.getBirthDayPart(); 46 String realBirthdayPart = this.createBirthDateParser().format( 47 birthDate); 48 result = result && (birthdayPart.equals(realBirthdayPart)); 49 } catch (Exception e) { 50 result = false; 51 } 52 cacheValidateResult = Boolean.valueOf(result);// TODO 53 // 完整身份证号码的省市县区检验规则 54 } 55 return cacheValidateResult; 56 } 57 /** 58 * 如果是15位身份证号码,则自动转换为18位 59 * 60 * @param cardNumber 61 * @return 62 */ 63 public CheckIdCard(String cardNumber) { 64 if (null != cardNumber) { 65 cardNumber = cardNumber.trim(); 66 if (OLD_CARD_NUMBER_LENGTH == cardNumber.length()) { 67 cardNumber = contertToNewCardNumber(cardNumber); 68 } 69 } 70 this.cardNumber = cardNumber; 71 } 72 public String getCardNumber() { 73 return cardNumber; 74 } 75 public String getAddressCode() { 76 this.checkIfValid(); 77 return this.cardNumber.substring(0, 6); 78 } 79 public Date getBirthDate() { 80 if (null == this.cacheBirthDate) { 81 try { 82 this.cacheBirthDate = this.createBirthDateParser().parse( 83 this.getBirthDayPart()); 84 } catch (Exception e) { 85 throw new RuntimeException("身份证的出生日期无效"); 86 } 87 } 88 return new Date(this.cacheBirthDate.getTime()); 89 } 90 public boolean isMale() { 91 return 1 == this.getGenderCode(); 92 } 93 public boolean isFemal() { 94 return false == this.isMale(); 95 } 96 /** 97 * 获取身份证的第17位,奇数为男性,偶数为女性 98 * 99 * @return 100 */ 101 private int getGenderCode() { 102 this.checkIfValid(); 103 char genderCode = this.cardNumber.charAt(NEW_CARD_NUMBER_LENGTH - 2); 104 return (((int) (genderCode - '0')) & 0x1); 105 } 106 private String getBirthDayPart() { 107 return this.cardNumber.substring(6, 14); 108 } 109 private SimpleDateFormat createBirthDateParser() { 110 return new SimpleDateFormat(BIRTH_DATE_FORMAT); 111 } 112 private void checkIfValid() { 113 if (false == this.validate()) { 114 throw new RuntimeException("身份证号码不正确!"); 115 } 116 } 117 /** 118 * 校验码(第十八位数): 119 * 120 * 十七位数字本体码加权求和公式 S = Sum(Ai * Wi), i = 0...16 ,先对前17位数字的权求和; 121 * Ai:表示第i位置上的身份证号码数字值 Wi:表示第i位置上的加权因子 Wi: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 122 * 2; 计算模 Y = mod(S, 11)< 通过模得到对应的校验码 Y: 0 1 2 3 4 5 6 7 8 9 10 校验码: 1 0 X 9 123 * 8 7 6 5 4 3 2 124 * 125 * @param cardNumber 126 * @return 127 */ 128 private static char calculateVerifyCode(CharSequence cardNumber) { 129 int sum = 0; 130 for (int i = 0; i < NEW_CARD_NUMBER_LENGTH - 1; i++) { 131 char ch = cardNumber.charAt(i); 132 sum += ((int) (ch - '0')) * VERIFY_CODE_WEIGHT[i]; 133 } 134 return VERIFY_CODE[sum % 11]; 135 } 136 /** 137 * 把15位身份证号码转换到18位身份证号码<br> 138 * 15位身份证号码与18位身份证号码的区别为:<br> 139 * 1、15位身份证号码中,"出生年份"字段是2位,转换时需要补入"19",表示20世纪<br> 140 * 2、15位身份证无最后一位校验码。18位身份证中,校验码根据根据前17位生成 141 * 142 * @param cardNumber 143 * @return 144 */ 145 private static String contertToNewCardNumber(String oldCardNumber) { 146 StringBuilder buf = new StringBuilder(NEW_CARD_NUMBER_LENGTH); 147 buf.append(oldCardNumber.substring(0, 6)); 148 buf.append("19"); 149 buf.append(oldCardNumber.substring(6)); 150 buf.append(CheckIdCard.calculateVerifyCode(buf)); 151 return buf.toString(); 152 } 153 }
3). 一个 Action 类 TestValidationAction
1 package com.struts.validation.app; 2 3 import com.opensymphony.xwork2.ActionSupport; 4 5 public class TestValidationAction extends ActionSupport{ 6 7 /** 8 * 9 */ 10 private static final long serialVersionUID = 1L; 11 12 private String idCard; 13 14 @Override 15 public String execute() throws Exception { 16 return SUCCESS; 17 } 18 19 public String getIdCard() { 20 return idCard; 21 } 22 23 public void setIdCard(String idCard) { 24 this.idCard = idCard; 25 } 26 27 28 }
4). 一个实现类 IdCardValidator
1 package com.struts.validation.app; 2 3 import com.opensymphony.xwork2.validator.ValidationException; 4 import com.opensymphony.xwork2.validator.validators.FieldValidatorSupport; 5 6 public class IdCardValidator extends FieldValidatorSupport { 7 8 @Override 9 public void validate(Object object) throws ValidationException { 10 // 1、获取请求字段名称及值 11 String fieldName = getFieldName(); 12 Object fieldValue = getFieldValue(fieldName, object); 13 14 // 2、验证参数值是否合法 15 CheckIdCard validate = new CheckIdCard((String) fieldValue); 16 Boolean result = false; 17 try { 18 result = validate.validate(); 19 } catch (Exception e) { 20 result = false; 21 e.printStackTrace(); 22 } 23 24 // 3、如果验证失败,返回失败信息 25 if (!result) { 26 addFieldError(fieldName, object); 27 } 28 29 } 30 31 }
5). 配置 validators.xml 文件 和 TestValidationAction-testValidation-validation.xml 文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator Definition 1.0//EN" "http://struts.apache.org/dtds/xwork-validator-definition-1.0.dtd"> <!-- START SNIPPET: validators-default --> <validators> <validator name="idCard" class="com.struts.validation.app.IdCardValidator" /> </validators>
<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0.2//EN" "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd"> <validators> <field name="idCard"> <field-validator type="idCard"> <message>身份证不正确!</message> </field-validator> </field> </validators>
至此一个身份证验证器就完成了.