SpringMVC 使用JSR-303进行校验 @Valid

时间:2024-01-20 13:42:15

注意:1 public String save(@ModelAttribute("house") @Valid House entity, BindingResult result,HttpServletRequest request,  Model model)     BindingResult 必须放在model,request前面

   2 validation-api-1.0.0.GA.jar是JDK的接口;hibernate-validator-4.2.0.Final.jar是对上述接口的实现。hibernate-validator-4.2.0.Final可以,但是hibernate-validator-4.3.0.Final报错

验证注解

验证的数据类型

说明

@AssertFalse

Boolean,boolean

验证注解的元素值是false

@AssertTrue

Boolean,boolean

验证注解的元素值是true

@NotNull

任意类型

验证注解的元素值不是null

@Null

任意类型

验证注解的元素值是null

@Min(value=值)

BigDecimal,BigInteger, byte,

short, int, long,等任何Number或CharSequence(存储的是数字)子类型

验证注解的元素值大于等于@Min指定的value值

@Max(value=值)

和@Min要求一样

验证注解的元素值小于等于@Max指定的value值

@DecimalMin(value=值)

和@Min要求一样

验证注解的元素值大于等于@ DecimalMin指定的value值

@DecimalMax(value=值)

和@Min要求一样

验证注解的元素值小于等于@ DecimalMax指定的value值

@Digits(integer=整数位数, fraction=小数位数)

和@Min要求一样

验证注解的元素值的整数位数和小数位数上限

@Size(min=下限, max=上限)

字符串、Collection、Map、数组等

验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小

@Past

java.util.Date,

java.util.Calendar;

Joda Time类库的日期类型

验证注解的元素值(日期类型)比当前时间早

@Future

与@Past要求一样

验证注解的元素值(日期类型)比当前时间晚

@NotBlank

CharSequence子类型

验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的首位空格

@Length(min=下限, max=上限)

CharSequence子类型

验证注解的元素值长度在min和max区间内

@NotEmpty

CharSequence子类型、Collection、Map、数组

验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)

@Range(min=最小值, max=最大值)

BigDecimal,BigInteger,CharSequence, byte, short, int, long等原子类型和包装类型

验证注解的元素值在最小值和最大值之间

@Email(regexp=正则表达式,

flag=标志的模式)

CharSequence子类型(如String)

验证注解的元素值是Email,也可以通过regexp和flag指定自定义的email格式

@Pattern(regexp=正则表达式,

flag=标志的模式)

String,任何CharSequence的子类型

验证注解的元素值与指定的正则表达式匹配

@Valid

任何非原子类型

指定递归验证关联的对象;

如用户对象中有个地址对象属性,如果想在验证用户对象时一起验证地址对象的话,在地址对象上加@Valid注解即可级联验证

一、准备校验时使用的JAR

SpringMVC 使用JSR-303进行校验 @Valid

说明:

validation-api-1.0.0.GA.jar是JDK的接口;

hibernate-validator-4.2.0.Final.jar是对上述接口的实现。

------------------------------------------------------------------------------------------------

新增一个测试的pojo bean ,增加jsr 303格式的验证annotation

  1. @NotEmpty
  2. private String userName;
  3. @Email
  4. private String email;

在controller 类中的handler method中,对需要验证的对象前增加@Valid 标志

  1. @RequestMapping("/valid")
  2. public String valid(@ModelAttribute("vm") [color=red]@Valid[/color] ValidModel vm, BindingResult result) {
  3. if (result.hasErrors()) {
  4. return "validResult";
  5. }
  6. return "helloworld";
  7. }

增加显示验证结果的jsp如下

  1. <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
  2. <html>
  3. <head>
  4. <title>Reservation Form</title>
  5. <style>
  6. .error {
  7. color: #ff0000;
  8. font-weight: bold;
  9. }
  10. </style>
  11. </head>
  12. <body>
  13. <form:form method="post" modelAttribute="vm">
  14. <form:errors path="*" cssClass="error" />
  15. <table>
  16. <tr>
  17. <td>Name</td>
  18. <td><form:input path="userName" />
  19. </td>
  20. <td><form:errors path="userName" cssClass="error" />
  21. </td>
  22. </tr>
  23. <tr>
  24. <td>email</td>
  25. <td><form:input path="email" />
  26. </td>
  27. <td><form:errors path="email" cssClass="error" />
  28. </td>
  29. </tr>
  30. <tr>
  31. <td colspan="3"><input type="submit" />
  32. </td>
  33. </tr>
  34. </table>
  35. </form:form>
  36. </body>
  37. </html>

访问 http://localhost:8080/springmvc/valid?userName=winzip&email=winzip 
查看验证结果。 
二:自定义jsr 303格式的annotation 
参考hibernate validator 4 reference 手册中3.1节,增加一个自定义要求输入内容为定长的annotation验证类 
新增annotation类定义

  1. @Target( { METHOD, FIELD, ANNOTATION_TYPE })
  2. @Retention(RUNTIME)
  3. @Constraint(validatedBy = FixLengthImpl.class)
  4. public @interface FixLength {
  5. int length();
  6. String message() default "{net.zhepu.web.valid.fixlength.message}";
  7. Class<?>[] groups() default {};
  8. Class<? extends Payload>[] payload() default {};
  9. }

及具体的验证实现类如下

  1. public class FixLengthImpl implements ConstraintValidator<FixLength, String> {
  2. private int length;
  3. @Override
  4. public boolean isValid(String validStr,
  5. ConstraintValidatorContext constraintContext) {
  6. if (validStr.length() != length) {
  7. return false;
  8. } else {
  9. return true;
  10. }
  11. }
  12. @Override
  13. public void initialize(FixLength fixLen) {
  14. this.length = fixLen.length();
  15. }
  16. }

为使自定义验证标注的message正常显示,需要修改servlet context配置文件,新增messageSource bean,如下

  1. <bean id="messageSource"
  2. class="org.springframework.context.support.ReloadableResourceBundleMessageSource"
  3. p:fallbackToSystemLocale="true" p:useCodeAsDefaultMessage="false"
  4. p:defaultEncoding="UTF-8">
  5. <description>Base message source to handle internationalization
  6. </description>
  7. <property name="basenames">
  8. <list>
  9. <!-- main resources -->
  10. <value>classpath:valid/validation</value>
  11. </list>
  12. </property>
  13. </bean>

表示spring 将从路径valid/validation.properties中查找对于的message。 
新增valid bean 引用新增的messageSource bean,表示valid message将从messageSource bean 注入。

  1. <bean id="validator"
  2. class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"
  3. p:validationMessageSource-ref="messageSource">
  4. <description>Enable the bean validation provider, and configure it to
  5. use the messageSource when resolving properties</description>
  6. </bean>

修改 <mvc:annotation-driven> 增加对validator bean的引用

  1. <mvc:annotation-driven validator="validator" />

最后修改之前新增的pojo bean ,新增一个mobileNO属性并增加对自定义标注的引用

  1. @FixLength(length=11)
  2. private String mobileNO;

在前端jsp中也增加新字段的支持

  1. <tr>
  2. <td>mobileno</td>
  3. <td><form:input path="mobileNO" />
  4. </td>
  5. <td><form:errors path="mobileNO" cssClass="error" />
  6. </td>
  7. </tr>

可访问url http://localhost:8080/springmvc/valid?userName=winzip&email=winzip&mobileNO=138188888 
来查看修改的结果。

三 json输入的验证 
Spring mvc 3.0.5中对于json格式的输入直接使用@valid标注有问题,目前这个bug还未修复 (见 SPR-6709),预计在3.1 m2版本中会修复。 
在此之前,可以通过如下几种方式来对json(或xml)格式的输入来进行验证。 
1:在handler method中直接对输入结果进行验证

  1. @RequestMapping("/validJson1")
  2. @ResponseBody
  3. public JsonResult processSubmitjson(@RequestBody ValidModel vm,
  4. HttpServletRequest request) {
  5. JsonResult jsonRst = new JsonResult();
  6. Set<ConstraintViolation<ValidModel>> set = validator.validate(vm);
  7. for (ConstraintViolation<ValidModel> violation : set) {
  8. String propertyPath = violation.getPropertyPath().toString();
  9. ;
  10. String message = violation.getMessage();
  11. log.error("invalid value for: '" + propertyPath + "': "
  12. + message);
  13. }
  14. if (!set.isEmpty()){
  15. jsonRst.setSuccess(false);
  16. jsonRst.setMsg("输入有误!");
  17. return jsonRst;
  18. }
  19. jsonRst.setSuccess(true);
  20. jsonRst.setMsg("输入成功!");
  21. return jsonRst;
  22. }

可通过修改后的helloworld.jsp中的json valid test1按钮进行调用测试。

2:将此验证逻辑封装为一个AOP,当需验证的对象前有@valid标注和@RequestBody标注时开始验证 
新增handler method如下

  1. @RequestMapping("/validJson2")
  2. @ResponseBody
  3. public JsonResult testJson4(@RequestBody @Valid ValidModel vm){
  4. log.info("handle json for valid");
  5. return new JsonResult(true,"return ok");
  6. }

这里没有对输入值做任何验证,所有的验证都在AOP中完成。 
修改pom.xml增加对AOP相关类库的引用。

  1. <dependency>
  2. <groupId>org.aspectj</groupId>
  3. <artifactId>aspectjweaver</artifactId>
  4. <version>1.6.11</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>cglib</groupId>
  8. <artifactId>cglib</artifactId>
  9. <version>2.2.2</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework</groupId>
  13. <artifactId>spring-aop</artifactId>
  14. <version>${org.springframework.version}</version>
  15. </dependency>

修改servet context xml,增加对aop的支持。

  1. <!-- enable Spring AOP support -->
  2. <aop:aspectj-autoproxy proxy-target-class="true" />

最后,新增AOP类

  1. public class CustomerValidatorAOP {
  2. private Validator validator;
  3. @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
  4. private void controllerInvocation() {
  5. }
  6. @Around("controllerInvocation()")
  7. public Object aroundController(ProceedingJoinPoint joinPoint) throws Throwable {
  8. MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
  9. Method method = methodSignature.getMethod();
  10. Annotation[] annotationList = method.getAnnotations();
  11. /* for(Annotation anno:annotationList){
  12. System.out.println(ResponseBody.class.isInstance(anno));
  13. }
  14. */
  15. Annotation[][] argAnnotations = method.getParameterAnnotations();
  16. String[] argNames = methodSignature.getParameterNames();
  17. Object[] args = joinPoint.getArgs();
  18. for (int i = 0; i < args.length; i++) {
  19. if (hasRequestBodyAndValidAnnotations(argAnnotations[i])) {
  20. Object ret = validateArg(args[i], argNames[i]);
  21. if(ret != null){
  22. return ret;
  23. }
  24. }
  25. }
  26. return joinPoint.proceed(args);
  27. }
  28. private boolean hasRequestBodyAndValidAnnotations(Annotation[] annotations) {
  29. if (annotations.length < 2)
  30. return false;
  31. boolean hasValid = false;
  32. boolean hasRequestBody = false;
  33. for (Annotation annotation : annotations) {
  34. if (Valid.class.isInstance(annotation))
  35. hasValid = true;
  36. else if (RequestBody.class.isInstance(annotation))
  37. hasRequestBody = true;
  38. if (hasValid && hasRequestBody)
  39. return true;
  40. }
  41. return false;
  42. }
  43. private JsonResult validateArg(Object arg, String argName) {
  44. BindingResult result = getBindingResult(arg, argName);
  45. validator.validate(arg, result);
  46. if (result.hasErrors()) {
  47. JsonResult jsonresult = new JsonResult();
  48. jsonresult.setSuccess(false);
  49. jsonresult.setMsg("fail");
  50. return jsonresult;
  51. }
  52. return null;
  53. }
  54. private BindingResult getBindingResult(Object target, String targetName) {
  55. return new BeanPropertyBindingResult(target, targetName);
  56. }
  57. @Required
  58. public void setValidator(Validator validator) {
  59. this.validator = validator;
  60. }

这里只考虑了输入为json格式的情况,仅仅作为一种思路供参考,实际使用时需要根据项目具体情况进行调整。 
可通过修改后的helloworld.jsp中的json valid test2按钮进行调用测试。

原文:http://starscream.iteye.com/blog/1068905