SpringMVC中数据校验, 我们往往采用JSR-303结合Hibernate Validation实现来进行数据校验.
JSR-303提供的默认注解是 javax.validation.Valid, 但这个注解有个缺陷, 不支持分组. SpringMVC注意到了这个问题, 提供了另一个注解 org.springframework.validation.annotation.Validated, 使用value注解属性来进行分组. 并结合Hibernate的验证注解来实现对数据的校验.
但我们在实际使用中, 发现存在有如下的问题:
-
分组不够明晰. @Validated注解value类型为Class[]类型, 而验证分组名主要是根据调用方法名来区分的. 为了分组, 需要创建不同的Class对象.
-
验证功能有限. JSR-303及Hibernate中提供的验证注解只有 @NotNull, @NotEmpty, @Digits, @Pattern, @Max, @Min, @NotBlank, @Size, @Min, @Max等, 都是针对单个属性的数据校验. 不能满足对象属性之间的约束限制. 比如座机和手机号码只能输入一个; 或当性别为男性, 体重>120斤, 性别为女性, 体重>100斤 这种情况, 就不能校验了.
-
复用问题. 比如有个IP地址的正则规则, 当使用@Pattern正则匹配时, 需要每个@Pattern注解都配置正则表达式. 当然, Hibernate Validation提供了对 javax.validation.ConstraintValidator 扩展, 但还是需要些实现类. 不是很方便
-
以注解的方式对字段进行校验,虽然使用方便. 但代码看起来会比较乱. 尤其在分组校验时, 很难一眼看出要校验哪些字段. 校验的规则是什么.
基于以上原因. 我们开发了EasyValidator, 和SpringMVC的校验相结合,提供强大的校验规则, 实现数据的快速, 直观校验.
-
安装easyValidator. 可直接依赖,也可通过mvn install或deploy的方式安装到maven库中.
-
配置使用
2.1 在你的springmvc配置文件(对应DispatcherServlet)中添加
<!--1. 创建EasyValidator Bean对象--> <bean id="easyValidator" class="cn.xyz.chaos.validator.spring.EasyValidator" /> <!--2. 将easyValidator配置到springmvc中 --> <mvc:annotation-driven validator="easyValidator" ...> ...... </mvc:annotation-driven> <!--3. 配置EasyValidator拦截器 --> <mvc:interceptors> <bean class="cn.xyz.chaos.validator.spring.EasyValidatorInterceptor" /> ...... </mvc:interceptors>
2.2 easyValidator中已提供了默认的校验规则, 在classpath:validator/internal/validator.xml中.如果需要扩展校验规则, 请在项目的classpath: validator/validator.xml或validator/internal/validtor.xml中定义.举例:
<?xml version="1.0" encoding="UTF-8"?> <validators xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="classpath://validator/internal/validator.xsd"> <validator name="mail" extends="regex" args="\\w+@\\w+\\.\\w+" /> </validators>
2.3 对要校验的实体类定义校验规则. 对象规则文件放在classpath: validator/ 目录下, 文件名以*.entity.xml结尾. 举例:
<!--UserDTO.entity.xml--> <?xml version="1.0" encoding="UTF-8"?> <entity class="cn.xyz.chaos.validator.test.UserDTO" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="classpath://validator/val.xsd"> <!-- 字段校验规则定义集合 --> <fields> <!--定义每个字段的校验规则. 一个字段可有多个校验规则. 规则名称和validator.xml中配置的规则匹配. 使用cn.xyz.chaos.validator.action.FieldActionValidator验证--> <field property="logUsername"> <valid name="required" msg="用户名不能为空" /> <valid name="length" min="4" max="16" msg="用户名长度为4-16个字符" /> <valid name="regex" args="\w+" msg="用户名为字符a-z,1-9,_" /> </field> <!--字段可配置默认规则. 供组中字段校验使用--> <field property="logPassword" valid-alias="required,length,regex"> <valid name="required" msg="密码不能为空" /> <valid name="length" args="4,16" msg="密码长度为4-16个字符" /> <valid name="regex" args="\w+" msg="密码为字符a-z,1-9" /> <!--当校验规则相同, 但参数不同时, 可通过alias区别, 避免规则替换. 当alias未配置时, 使用name作为alias--> <valid alias="num" name="regex" args="\d+" msg="必须为数字" /> </field> <field property="logPassword2"> <valid name="required" msg="密码不能为空" /> <!--binary Operator规则使用--> <valid name="equal" args="logPassword,logPassword2" msg="两次输入不一致" /> </field> <field property="mobile"> <valid name="required" msg="手机号码不能为空" /> <valid name="regex" args="\d{11}" msg="手机号码格式错误" /> </field> <field property="phone"> <valid name="required" msg="电话号码不能为空" /> <valid name="regex" args="\d+" msg="电话号码格式错误" /> </field> </fields> <!--组--> <groups> <!--组规则定义. 一般来说, 一个请求对应的Controller方法对应一个分组. 但可能存在对应多个分组, 参考下面的@ValidEasy说明--> <!--组名一般为方法名--> <group name="login"> <field property="logUsername" /> <field property="logPassword" /> </group> <group name="register"> <field property="logUsername" /> <field property="logPassword" /> <!--脚本验证. 使用cn.xyz.chaos.validator.action.ScriptActionValidator--> <script test="target.logPassword == 'mima'"> <field property="logPassword2" /> </script> <script test="target.logUsername == 'mfan'"> <field property="mobile" /> <field property="phone" /> </script> </group> <group name="register1"> <field property="logUsername" /> <field property="logPassword" /> <script test="target.logUsername != 'mfan'"> <field property="mobile" /> <field property="phone" /> </script> </group> <group name="register2"> <field property="logUsername" /> <field property="logPassword" valid-alias="required,length,num" /> <field property="logPassword2" /> </group> </groups> </entity>
2.4 对需要验证的Controller方法中验证对象添加@ValidEasy注解. 注解value如果不配置, 默认使用方法名作为分组名称. 也可配置为多个分组, 验证时使用*.entity.xml中相应的多个分组结合验证.
2.5 验证不通过时, 错误信息集合可通过ModelAndView.getModel().get("_error")来获取.
校验器 | 名称 | 参数 | 说明 |
---|---|---|---|
RequiredValidator | required | - | 非空或不为"" |
NullValidator | null | - | 为空 |
NotNullValidator | notnull | - | 非空 |
NotBlankValidator | notblank | - | 可为空但不能为"" |
RegexValidator | regex | regex | 正则 |
LengthValidator | length | min; max | 字符串长度校验 |
MaxValidator | max | max | 数字或数字字符串最大值校验 |
MinValidator | min | min | 数字或数字字符串最小值校验 |
RangeValidator | range | min; max | 数字或数字字符串范围校验 |
RestrictValidator | restrict | restrict(多个值以逗号分隔) | 限定值校验 |
SizeValidator | size | min; max | 对集合类型指的是大小, 对字符类型指的是长度 |
CompareValidator | compare | flag(EQ,NE,LT,LE,GT,GE); ref(属性字段集合. 集合大小>=2. ) | 第一个属性和后续属性进行比较校验 |
EqualValidator | equal | ref(属性字段集合. 集合大小>=2. 多个字段以逗号分隔) | 属性是否相同校验. 扩展自CompareValidator |
AtMostValidator | atmost | max; ref(属性字段集合. 多个字段以逗号分隔) | 被校验对象最多有几个属性可以有值校验 |
AtLeastValidator | atleast | min; ref(属性字段集合. 多个字段以逗号分隔) | 被校验对象最少有几个属性可以有值校验 |
PastValidator | past | - | 日期对象在当前日期之前校验 |
FutureValidator | future | - | 日期对象在当前日期之后校验 |
UrlValidator | url | url | 对象或对象字面量是否符合URL规范校验 |
EmailValidator | - | 对象是否符合Email规范校验.对象可为空 | |
IdCardValidator | idcard | - | 身份证格式校验 |
扩展校验包括两部分内容:
扩展校验规则有两种方式:
-
继承AbstractValidator类实现验证器扩展XxxValidator
package cn.xyz.chaos.examples.showcase.web.controller.validator; import cn.xyz.chaos.validator.ValidContext; import cn.xyz.chaos.validator.validators.AbstractValidator; /** * 自定义扩展校验器 * * @author lvchenggang */ public class XxxValidator extends AbstractValidator { @Override public boolean isValid(Object object, ValidContext validContext) { // TODO具体校验规则 return false; } }
-
在validator.xml中配置此XxxValidator,并配置相关参数
<validators> <validator name="xxx" class="cn.xyz.chaos.examples.showcase.web.controller.validator.XxxValidator" /> </validators>
<validators>
<validator name="mail" extends="regex" args="\\w+@\\w+\\.\\w+" />
</validators>
-
自定义校验器, 继承ActionValidator类
package cn.xyz.chaos.validator.action; import java.util.Arrays; import java.util.List; import cn.xyz.chaos.validator.EasyFieldError; import cn.xyz.chaos.validator.EasyValidatorUtilities; import cn.xyz.chaos.validator.ValidContext; import cn.xyz.chaos.validator.data.Action; import cn.xyz.chaos.validator.data.Field; import cn.xyz.chaos.validator.data.FieldAction; import cn.xyz.chaos.validator.data.Valid; import cn.xyz.chaos.validator.utils.beans.BeanUtils; import cn.xyz.chaos.validator.validators.Validator; /** * <pre> * 字段校验器.一个字段对应多个Valid * </pre> * * @author lvchenggang * */ public class FieldActionValidator implements ActionValidator<FieldAction> { @Override public List<EasyFieldError> validator(EasyValidatorUtilities utilities, ValidContext validContext, FieldAction action, ActionValidatorChain chain) { Field field = action.getField(); List<Valid> valids = field.getValids(); for (Valid valid : valids) { Validator validator = utilities.getContext().getValidator(valid.getName()); if (validator != null) { validContext.setValid(valid); // 验证值 Object object = BeanUtils.getProperty(validContext.getTarget(), field.getProperty()); if (!validator.isValid(object, validContext)) { EasyFieldError error = new EasyFieldError(field.getProperty(), valid.getMsg(), valid.attrs()); validContext.getErrors().add(error); return Arrays.asList(error); } } } return null; } @Override public boolean supports(Action action) { return action instanceof FieldAction; } }
-
扩展xml校验执行器配置解析器. 解析器继承自ActionResolver. 比如:
<validators> <action-resolver class="cn.xyz.chaos.validator.action.xml.FieldActionResolver"/> </validators>
校验框架 | 优势 | 劣势 |
---|---|---|
EasyValidator | (1) 校验规则丰富, 支持自定义, 且扩展方便 (2) 支持对象上下文, 可进行校验属性和其他对象属性关联 (3) xml方式配置, 配置清晰明了 |
只能对Controller方法参数对象进行校验 |
HibernateValidator | (1) 注解校验. 配置简单. (2) 可对任意方法的参数对象进行校验 (3) 支持校验规则扩展 |
(1) 分组校验代码管理比较混乱 (2) 不支持对象属性关联 |