Spring MVC Bean 参数校验 @Validated

 

一、Bean Validation简介

 

JSR-303主要是对JavaBean进行验证,如方法级别(方法参数/返回值)、依赖注入等的验证是没有指定的。因此又有了JSR-349规范的产生。

Hibernate Validator(下载地址:http://www.hibernate.org/subprojects/validator.html);

 

二、Bean Validation在开发中的位置


 


 

 

上图摘自hibernate validator 参考文档,从图中可以看出,我们可以在任何位置实施验证。

 

1、表现层验证:SpringMVC提供对JSR-303的表现层验证;

2、业务逻辑层验证:Spring3.1提供对业务逻辑层的方法验证(当然方法验证可以出现在其他层,但笔者觉得方法验证应该验证业务逻辑);

3、DAO层验证:Hibernate提供DAO层的模型数据的验证(可参考hibernate validator参考文档的7.3. ORM集成)。

4、数据库端的验证:通过数据库约束来进行;

5、客户端验证支持:JSR-303也提供编程式验证支持。

 

对于DAO层和客户端验证支持不在我们示例范围,忽略,感兴趣的同学可以参考《hibernate validator reference》(有中文)。

  

在测试支持大家需要准备好如下jar包:

validation-api-1.0.0.GA.jar

hibernate-validator-4.2.0.Final.jar

 

四、Spring3.0支持表现层验证

可以参考我的《最新SpringMVC + spring3.1.1 + hibernate4.1.0 集成及常见问题总结》或《SpringMVC 使用JSR-303进行校验 @Valid》。

 

此处不再阐述。

 

spring已经支持方法界别的参数验证了,我们只需要配置Bean就好了,然后在需要验证的类上面加上@Validated就可以了

<!--  bean级别的校验 方法中的参数bean必须添加@Valid注解,后面紧跟着BindingResult result参数-->
<bean id="global-validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"></bean>
<!--  方法级别的校验  要校验的方法所在类必须添加@Validated注解-->
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"></bean>
<!--可以引用自己的 validator 配置,在本文中(下面)可以找到 validator 的参考配置,如果不指定则系统使用默认的 -->



虽然这个很简单,不过笔者在测试的时候总是报错 Unable to initialize ‘javax.el.ExpressionFactory’. Check that you have the EL dependencies on the classpath, or use ParameterMessageInterpolator instead,然后无法注入这两个Bean

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.4.0.Final</version>
</dependency>
<dependency>
    <groupId>org.glassfish.web</groupId>
    <artifactId>javax.el</artifactId>
    <version>2.2.6</version>
</dependency>

五、Spring3.0支持依赖注入验证(Bean Validation 1.1草案)

Spring3.0开始支持对依赖注入的依赖进行验证。Spring对依赖注入验证支持请参考《Spring开闭原则的表现-BeanPostProcessor扩展点-2》中的BeanValidationPostProcessor。

 

示例:

1、Bean组件类定义

Java代码  收藏代码

  1. public class UserModel {  
  2.     @NotNull(message = "user.username.null")  
  3.     @Pattern(regexp = "[a-zA-Z0-9_]{5,10}", message = "user.username.illegal")  
  4.     private String username;  
  5.     @Size(min = 5, max=10, message = "password.length.illegal")  
  6.     private String password;  
  7.     //省略setter/getter  
  8. }  

 2、开启依赖注入验证支持(spring-config-bean-validator.xml)

Java代码  收藏代码

  1. <!--注册Bean验证后处理器-->  
  2. <bean class="org.springframework.validation.beanvalidation.BeanValidationPostProcessor"/>  

 3、Bean的XML配置定义(spring-config-bean-validator.xml)

Java代码  收藏代码

  1. <bean id="user" class="com.sishuok.validator.UserModel">  
  2.     <property name="username" value="@"/>  
  3.     <property name="password" value="#"/>  
  4. </bean>  

 4、测试用例

Java代码  收藏代码

  1. @RunWith(value = SpringJUnit4ClassRunner.class)  
  2. @ContextConfiguration(value = {"classpath:spring-config-bean-validator.xml"})  
  3. public class BeanValidatorTest {  
  4.     @Autowired  
  5.     UserModel user;  
  6.     @Test  
  7.     public void test() {  
  8.     }  
  9. }  

 5、运行测试后,容器启动失败并将看到如下异常:

Java代码  收藏代码

  1. java.lang.IllegalStateException: Failed to load ApplicationContext  
  2. ……  
  3. Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'user' defined in class path resource [spring-config-bean-validator.xml]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanInitializationException: Bean state is invalid: password - password.length.illegal; username - user.username.illegal  
  4. ……  
  5. Caused by: org.springframework.beans.factory.BeanInitializationException: Bean state is invalid: password - password.length.illegal; username - user.username.illegal  

 我们可以看出 用户名验证失败。

 

六、Spring3.1支持方法级别验证

Spring3.1开始支持方法级别的验证。Spring对方法级别的验证支持请参考《Spring开闭原则的表现-BeanPostProcessor扩展点-2》中的MethodValidationPostProcessor。

 

有了方法级别验证,我们就能够更加简单的在Java世界进行契约式设计了,关于契约式设计请参考《建造无错软件:契约式设计引论》。

 

没有MethodValidationPostProcessor之前我们可能这样验证:

Java代码  收藏代码

  1. public UserModel get(Integer uuid) {  
  2.     //前置条件  
  3.     Assert.notNull(uuid);  
  4.     Assert.isTrue(uuid > 0"uuid must lt 0");  
  5.   
  6.     //获取 User Model  
  7.     UserModel user = new UserModel(); //此处应该从数据库获取  
  8.   
  9.     //后置条件  
  10.     Assert.notNull(user);  
  11.     return user;  
  12. }  

前置条件和后置条件的书写是很烦人的工作。

 

有了MethodValidationPostProcessor之后我们可以这样验证:

Java代码  收藏代码

  1. public @NotNull UserModel get2(@NotNull @Size(min = 1) Integer uuid) {  
  2.     //获取 User Model  
  3.     UserModel user = new UserModel(); //此处应该从数据库获取  
  4.     return user;  
  5. }  

 

前置条件的验证:在方法的参数上通过Bean Validation注解进行实施;

后置条件的验证:直接在返回值上通过Bean Validation注解进行实施。 

非常好,非常好,自此我们可以在Java世界进行更完美的契约式编程了。

 

示例:

1、Service类定义

Java代码  收藏代码

  1. @Validated      //① 告诉MethodValidationPostProcessor此Bean需要开启方法级别验证支持  
  2. public class UserService {  
  3.     public @NotNull UserModel get2(@NotNull @Min(value = 1) Integer uuid) { //②声明前置条件/后置条件  
  4.         //获取 User Model  
  5.         UserModel user = new UserModel(); //此处应该从数据库获取  
  6.         if(uuid > 100) {//方便后置添加的判断(此处假设传入的uuid>100 则返回null)  
  7.             return null;  
  8.         }  
  9.         return user;  
  10.     }  
  11. }  

 2、开启Spring3.1对方法级别验证支持(spring-config-method-validator.xml)

Java代码  收藏代码

  1. <!--注册方法验证的后处理器-->  
  2. <bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>  

 3、Bean的XML配置定义(spring-config-method-validator.xml)

Java代码  收藏代码

  1. <bean id="userService" class="com.sishuok.validator.UserService"/>  

 4、测试用例

Java代码  收藏代码

  1. @RunWith(value = SpringJUnit4ClassRunner.class)  
  2. @ContextConfiguration(value = {"classpath:spring-config-method-validator.xml"})  
  3. public class MethodValidatorTest {  
  4.     @Autowired  
  5.     UserService userService;  
  6.     @Test  
  7.     public void testConditionSuccess() {//① 正常流程   
  8.         userService.get2(1);  
  9.     }  
  10.     @Test(expected = org.hibernate.validator.method.MethodConstraintViolationException.class)  
  11.     public void testPreCondtionFail() { //②错误的uuid(即前置条件不满足)  
  12.         userService.get2(0);  
  13.     }  
  14.   
  15.     @Test(expected = org.hibernate.validator.method.MethodConstraintViolationException.class)  
  16.     public void testPostCondtionFail() { //③不满足后置条件的返回值  
  17.         userService.get2(10000);  
  18.     }  
  19. }  

通过如上测试,我们可以看出Spring3.1已经非常好的支持契约式编程了。

 

注意,在使用方法级别验证时:

1、由于Bean Validation1.1正处于草案状态,Spring3.1无法支持原生的Bean Validation1.1,在未来的Bean Validation1.1发布时会直接使用原生的。

2、Spring3.1需要使用Hibernate Validator 4.2及更高版本。

 

 

 

七、Spring MVC @Validated的特殊用法

 

 

@Valid是javax.validation里的。

@Validated是@Valid 的一次封装,是spring提供的校验机制使用。@Valid不提供分组功能

 

@Validated的特殊用法

1、分组

当一个实体类需要多种验证方式时,例:对于一个实体类的id来说,新增的时候是不需要的,对于更新时是必须的。

可以通过groups对验证进行分组

分组接口类(通过向groups分配不同类的class对象,达到分组目的):

 

[html] view plain copy  

  1. package com.valid.interfaces;  
  2.   
  3. public interface First {  
  4.   
  5. }  

 

实体类:

 

[html] view plain copy  

  1. package com.valid.pojo;  
  2.   
  3. import javax.validation.constraints.Size;  
  4. import org.hibernate.validator.constraints.NotEmpty;  
  5.   
  6. import com.valid.interfaces.First;  
  7.   
  8. public class People {  
  9.       
  10.     //在First分组时,判断不能为空  
  11.     @NotEmpty(groups={First.class})  
  12.     private String id;  
  13.       
  14.     //name字段不为空,且长度在3-8之间  
  15.     @NotEmpty  
  16.     @Size(min=3,max=8)  
  17.     private String name;  
  18.   
  19.     public String getName() {  
  20.         return name;  
  21.     }  
  22.   
  23.     public void setName(String name) {  
  24.         this.name = name;  
  25.     }  
  26.   
  27.     public String getId() {  
  28.         return id;  
  29.     }  
  30.   
  31.     public void setId(String id) {  
  32.         this.id = id;  
  33.     }  
  34. }  

 

注:

(1)不分配groups,默认每次都要进行验证

(2)对一个参数需要多种验证方式时,也可通过分配不同的组达到目的。例:

 

[html] view plain copy  

  1. @NotEmpty(groups={First.class})  
  2. @Size(min=3,max=8,groups={Second.class})  
  3. private String name;  

 

控制类:

 

[html] view plain copy  

  1. package com.valid.controller;  
  2.   
  3. import org.springframework.stereotype.Controller;  
  4. import org.springframework.validation.BindingResult;  
  5. import org.springframework.validation.annotation.Validated;  
  6. import org.springframework.web.bind.annotation.RequestMapping;  
  7. import org.springframework.web.bind.annotation.ResponseBody;  
  8.   
  9. import com.valid.interfaces.First;  
  10. import com.valid.pojo.People;  
  11.   
  12. @Controller  
  13. public class FirstController {  
  14.       
  15.     @RequestMapping("/addPeople")  
  16.     //不需验证ID  
  17.     public @ResponseBody String addPeople(@Validated People p,BindingResult result)  
  18.     {  
  19.         System.out.println("people's ID:" + p.getId());  
  20.         if(result.hasErrors())  
  21.         {  
  22.             return "0";  
  23.         }  
  24.         return "1";  
  25.     }  
  26.       
  27.     @RequestMapping("/updatePeople")  
  28.     //需要验证ID  
  29.     public @ResponseBody String updatePeople(@Validated({First.class}) People p,BindingResult result)  
  30.     {  
  31.         System.out.println("people's ID:" + p.getId());  
  32.         if(result.hasErrors())  
  33.         {  
  34.             return "0";  
  35.         }  
  36.         return "1";  
  37.     }  
  38. }  


注:

 

@Validated没有添加groups属性时,默认验证没有分组的验证属性,如该例子:People的name属性。

@Validated没有添加groups属性时,所有参数的验证类型都有分组(即本例中People的name的@NotEmpty、@Size都添加groups属性),则不验证任何参数

2、组序列

默认情况下,不同组别的约束验证是无序的,然而在某些情况下,约束验证的顺序却很重要。

例:

(1)第二个组中的约束验证依赖于一个稳定状态来运行,而这个稳定状态是由第一个组来进行验证的。

(2)某个组的验证比较耗时,CPU 和内存的使用率相对比较大,最优的选择是将其放在最后进行验证。因此,在进行组验证的时候尚需提供一种有序的验证方式,这就提出了组序列的概念。
一个组可以定义为其他组的序列,使用它进行验证的时候必须符合该序列规定的顺序。在使用组序列验证的时候,如果序列前边的组验证失败,则后面的组将不再给予验证。

分组接口类 (通过@GroupSequence注解对组进行排序):

 

[html] view plain copy  

  1. package com.valid.interfaces;  
  2.   
  3. public interface First {  
  4.   
  5. }  

[html] view plain copy  

  1. package com.valid.interfaces;  
  2.   
  3. public interface Second {  
  4.   
  5. }  

 

 

[html] view plain copy  

  1. package com.valid.interfaces;  
  2.   
  3. import javax.validation.GroupSequence;  
  4.   
  5. @GroupSequence({First.class,Second.class})  
  6. public interface Group {  
  7.   
  8. }  

实体类:

 

 

[html] view plain copy  

  1. package com.valid.pojo;  
  2.   
  3. import javax.validation.constraints.Size;  
  4. import org.hibernate.validator.constraints.NotEmpty;  
  5.   
  6. import com.valid.interfaces.First;  
  7. import com.valid.interfaces.Second;  
  8.   
  9. public class People {  
  10.       
  11.     //在First分组时,判断不能为空  
  12.     @NotEmpty(groups={First.class})  
  13.     private String id;  
  14.       
  15.     //name字段不为空,且长度在3-8之间  
  16.     @NotEmpty(groups={First.class})  
  17.     @Size(min=3,max=8,groups={Second.class})  
  18.     private String name;  
  19.   
  20.     public String getName() {  
  21.         return name;  
  22.     }  
  23.   
  24.     public void setName(String name) {  
  25.         this.name = name;  
  26.     }  
  27.   
  28.     public String getId() {  
  29.         return id;  
  30.     }  
  31.   
  32.     public void setId(String id) {  
  33.         this.id = id;  
  34.     }  
  35. }  

控制类:

 

 

[html] view plain copy  

  1. package com.valid.controller;  
  2.   
  3. import org.springframework.stereotype.Controller;  
  4. import org.springframework.validation.BindingResult;  
  5. import org.springframework.validation.annotation.Validated;  
  6. import org.springframework.web.bind.annotation.RequestMapping;  
  7. import org.springframework.web.bind.annotation.ResponseBody;  
  8.   
  9. import com.valid.interfaces.Group;  
  10. import com.valid.pojo.People;  
  11. import com.valid.pojo.Person;  
  12.   
  13. @Controller  
  14. public class FirstController {  
  15.       
  16.     @RequestMapping("/addPeople")  
  17.     //不需验证ID  
  18.     public @ResponseBody String addPeople(@Validated({Group.class}) People p,BindingResult result)  
  19.     {  
  20.         if(result.hasErrors())  
  21.         {  
  22.             return "0";  
  23.         }  
  24.         return "1";  
  25.     }  
  26. }  

3、验证多个对象

 

一个功能方法上处理多个模型对象时,需添加多个验证结果对象

 

[html] view plain copy  

  1. package com.valid.controller;  
  2.   
  3. import org.springframework.stereotype.Controller;  
  4. import org.springframework.validation.BindingResult;  
  5. import org.springframework.validation.annotation.Validated;  
  6. import org.springframework.web.bind.annotation.RequestMapping;  
  7. import org.springframework.web.bind.annotation.ResponseBody;  
  8.   
  9. import com.valid.pojo.People;  
  10. import com.valid.pojo.Person;  
  11.   
  12. @Controller  
  13. public class FirstController {  
  14.       
  15.     @RequestMapping("/addPeople")  
  16.     public @ResponseBody String addPeople(@Validated People p,BindingResult result,@Validated Person p2,BindingResult result2)  
  17.     {  
  18.         if(result.hasErrors())  
  19.         {  
  20.             return "0";  
  21.         }  
  22.         if(result2.hasErrors())  
  23.         {  
  24.             return "-1";  
  25.         }  
  26.         return "1";  
  27.     }  
  28. }  

 

 

 

八、JSR303定义的校验类型

复制代码

空检查

@Null       验证对象是否为null

@NotNull    验证对象是否不为null, 无法查检长度为0的字符串

@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.

@NotEmpty 检查约束元素是否为NULL或者是EMPTY.

 

Booelan检查

@AssertTrue     验证 Boolean 对象是否为 true  

@AssertFalse    验证 Boolean 对象是否为 false  

 

长度检查

@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内  

@Length(min=, max=) Validates that the annotated string is between min and max included.

 

日期检查

@Past           验证 Date 和 Calendar 对象是否在当前时间之前  

@Future     验证 Date 和 Calendar 对象是否在当前时间之后  

@Pattern    验证 String 对象是否符合正则表达式的规则

 

数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null

@Min            验证 Number 和 String 对象是否大等于指定的值  

@Max            验证 Number 和 String 对象是否小等于指定的值  

@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度

@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度

@Digits     验证 Number 和 String 的构成是否合法  

@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。

 

@Range(min=, max=) Checks whether the annotated value lies between (inclusive) the specified minimum and maximum.

@Range(min=10000,max=50000,message="range.bean.wage")
private BigDecimal wage;

 

@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)

@CreditCardNumber信用卡验证

@Email  验证是否是邮件地址,如果为null,不进行验证,算通过验证。

@ScriptAssert(lang= ,script=, alias=)

@URL(protocol=,host=, port=,regexp=, flags=)

 

 

 

相关: http://jinnianshilongnian.iteye.com/blog/1990081

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值