Spring4 对Bean Validation规范的新支持(方法级别验证)


Bean Validation standardizes constraint definition, declaration and validation for the Java platform.

大体意思是:Bean Validation 标准化了Java平台的约束定义、描述、和验证。

 

详细了解请参考:http://beanvalidation.org/

 

Bean Validation现在一个有两个规范:

 



This JSR will define a meta-data model and API for JavaBeanTM validation based on annotations, with overrides and extended meta-data through the use of XML validation descriptors.

定义了基于注解方式的JavaBean验证元数据模型和API,也可以通过XML进行元数据定义,但注解将覆盖XML的元数据定义。

 

详细了解请参考:http://jcp.org/en/jsr/detail?id=303

 

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

 



Bean Validation standardizes constraint definition, declaration and validation for the Java platform.

Bean Validation 标准化了Java平台的约束定义、描述、和验证。

 


对Bean Validation的详细介绍可参考Bean Validation官网查看http://beanvalidation.org/


Bean Validation 1.0的参考实现有Hibernate Validator(下载地址:https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/);

 



 


 

 

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

 

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

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

3、DAO层验证:Hibernate提供DAO层的模型数据的验证。

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

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

 

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

  

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

 
 
  1. <dependency>
  2. <groupId>org.hibernate</groupId>
  3. <artifactId>hibernate-validator</artifactId>
  4. <version>5.3.4</version>
  5. </dependency>
      


 现在我们纯粹的只是利用Bean Validation和hibernate的实现Hibernate Validator来做一个校验


  
  
  1. package com.somnus.validation.model;
  2. import javax.validation.constraints.NotNull;
  3. import javax.validation.constraints.Pattern;
  4. import javax.validation.constraints.Size;
  5. import org.apache.commons.lang3.builder.ToStringBuilder;
  6. import org.apache.commons.lang3.builder.ToStringStyle;
  7. public class User {
  8. @NotNull
  9. @Pattern(regexp = "[a-zA-Z0-9_]{5,10}" , message = "{user.username.illegal}")
  10. private String username;
  11. @Size(min = 6, max=10)
  12. private String password;
  13. //省略setter/getter
  14. public User(String username, String password) {
  15. super();
  16. this.username = username;
  17. this.password = password;
  18. }
  19. public User() {
  20. super();
  21. }
  22. public String toString() {
  23. return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
  24. }
  25. }

调用 JSR 349 API 进行校验

  
  
  1. package com.somnus.solo;
  2.  
  3. import java.lang.reflect.Method;
  4. import java.text.ParseException;
  5. import java.util.Date;
  6. import java.util.Set;
  7.  
  8. import javax.validation.ConstraintViolation;
  9. import javax.validation.Validation;
  10. import javax.validation.Validator;
  11. import javax.validation.executable.ExecutableValidator;
  12.  
  13. import org.apache.commons.lang3.time.DateUtils;
  14. import org.hibernate.validator.HibernateValidator;
  15. import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
  16. import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator;
  17. import org.junit.Test;
  18.  
  19. import com.somnus.solo.validation.UserValidator;
  20. import com.somnus.solo.validation.model.User;
  21.  
  22. public class ValidationTest {
  23.  
  24. /**
  25. * 当前demo中所有的关于 都不再使用Bean Validation 1.0(JSR-303)旧标准,
  26. * 目前使用的是 Bean Validation 1.1(JSR-349)
  27. *
  28. * 此方法是用来学习Validator的使用
  29. * 1、如何拿到Validator的hibernate实现
  30. * 2、如何拿到校验失败的相关信息
  31. * 3、User的字段tranDate上面使用了一个自定义注解(如果你需要自定义,可以参照这个)
  32. * User最终在这里是校验不通过的,因为要求了tranDate必须是大于或者今天
  33. * @throws ParseException
  34. */
  35. @Test
  36. public void defaultValidator() throws ParseException{
  37. Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
  38. User user = new User("admin","123456",DateUtils.parseDate("2014-11-11", new String[] {"yyyy-MM-dd"}));
  39. Set<ConstraintViolation<User>> violations = validator.validate(user);
  40. for(ConstraintViolation<User> data:violations){
  41. System.out.println(data.getPropertyPath().toString() + ":" + data.getMessage());
  42. }
  43. }
  44.  
  45. /**
  46. * 此方法和是对上面一个方法的补充,上面都是用的默认值
  47. * 而这里面拿到Validator的都是自己一个个指定相关配置,
  48. * 1、比如指定实现类HibernateValidator
  49. * 2、比如指定properties资源文件
  50. * 3、比如指定是否返回所有校验字段的异常信息(默认返回所有)
  51. */
  52. @Test
  53. public void hibernateValidator(){
  54. Validator validator = Validation.byProvider(HibernateValidator.class).configure()
  55. .messageInterpolator(new ResourceBundleMessageInterpolator(new PlatformResourceBundleLocator("message/validate")))
  56. .failFast(false)
  57. .buildValidatorFactory().getValidator();
  58. User user = new User("adm#in","12345",new Date());
  59. Set<ConstraintViolation<User>> violations = validator.validate(user);
  60. for(ConstraintViolation<User> data:violations){
  61. System.out.println(data.getPropertyPath().toString() + ":" + data.getMessage());
  62. }
  63. }
  64.  
  65. /**
  66. * 此方法是用来验证Bean Validation规范的新支持(方法级别验证),这里验证方法中的参数是否符合规范
  67. * 注意:这里需要拿到的不再是Validator,而是ExecutableValidator
  68. * 如果方法中的参数是对象model类型,记得加@Valid 注解
  69. * @throws NoSuchMethodException
  70. */
  71. @Test
  72. public void validateParameters() throws NoSuchMethodException{
  73. ExecutableValidator executableValidator = Validation.byProvider(HibernateValidator.class).configure()
  74. .messageInterpolator(new ResourceBundleMessageInterpolator(new PlatformResourceBundleLocator("message/validate")))
  75. .failFast(false)
  76. .buildValidatorFactory().getValidator().forExecutables();
  77. UserValidator object = new UserValidator();
  78. Method method = object.getClass().getMethod( "verify", new Class[]{User.class} );
  79. Object[] parameterValues = {new User("adm#in","12345",new Date())};
  80. Set<ConstraintViolation<UserValidator>> violations = executableValidator.validateParameters(
  81. object,
  82. method,
  83. parameterValues
  84. );
  85. for(ConstraintViolation<UserValidator> data:violations){
  86. System.out.println(data.getPropertyPath().toString() + ":" + data.getMessage());
  87. }
  88. }
  89.  
  90. /**
  91. * 此方法是用来验证Bean Validation规范的新支持(方法级别验证),这里验证方法中的返回值是否符合规范
  92. * 这些写法在硬编码这里略显笨拙,但是一旦和切面一起使用将是一把利器,再也不用傻傻的在每个方法中去做校验了
  93. * 本项目已经做了相关示例,详细请见src/main/java中的【com.somnus.solo.support.aspect.ValidationAspect】
  94. * @throws NoSuchMethodException
  95. */
  96. @Test
  97. public void validateReturnValue() throws NoSuchMethodException{
  98. ExecutableValidator executableValidator = Validation.buildDefaultValidatorFactory().getValidator().forExecutables();
  99. UserValidator object = new UserValidator();
  100. Method method = object.getClass().getMethod( "getUsers", new Class[]{} );
  101. Object returnValue = object.getUsers();
  102. Set<ConstraintViolation<UserValidator>> violations = executableValidator.validateReturnValue(
  103. object,
  104. method,
  105. returnValue
  106. );
  107. for(ConstraintViolation<UserValidator> data:violations){
  108. System.out.println(data.getPropertyPath().toString() + ":" + data.getMessage());
  109. }
  110. }
  111.  
  112. }



针对上述我给出的两组测试,区别只在于如何获得Validator,从代码上看去差别挺大,其实本质上没有任何区别,都是获得ValidationImpl

  
  
  1. /**
  2. * The main Bean Validation class. This is the core processing class of Hibernate Validator.
  3. *
  4. * @author Emmanuel Bernard
  5. * @author Hardy Ferentschik
  6. * @author Gunnar Morling
  7. * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI
  8. * @author Guillaume Smet
  9. */
  10. public class ValidatorImpl implements Validator, ExecutableValidator {

我们可以通过看源码的方式,来了解其中的区别。


这里之所以要讲清楚为什么有这两种方式,其实也是为Spring框架对如果引入Validator做铺垫,具体详情请看下文,并且找到相关bean的源码




我们如果不给这两个bean手动注入Validator,它也可以拿到hibernate提供的ValidatorImpl

另外我们通常也会在Spring项目中碰到有开发者会使用


然后就有了如下配置

 
 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5.  
  6. <bean id="validatemessageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
  7. <property name="basename" value="classpath:message/validate"/>
  8. <property name="fileEncodings" value="utf-8"/>
  9. <property name="cacheSeconds" value="120"/>
  10. </bean>
  11. <bean name="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
  12. <!-- 不设置则默认去找org.hibernate.validator.HibernateValidator-->
  13. <property name="providerClass" value="org.hibernate.validator.HibernateValidator" />
  14. <!--不设置则默认为classpath下的 ValidationMessages.properties -->
  15. <property name="validationMessageSource" ref="validatemessageSource"/>
  16. <!-- 不设置则默认为false,true和false的区别在于:如果为true则不管验证项有多少个为失败的,
  17. 都只返回解析到的第一个,其余再返回,如果为false则返回所有验证失败项 -->
  18. <property name="validationPropertyMap">
  19. <map>
  20. <entry key="hibernate.validator.fail_fast" value="true"/>
  21. </map>
  22. </property>
  23. </bean>
  24. </beans>

通过我写的注释,想必你已明白,貌似相关注入就算我全部不写这个Validator也是可以被创建出来的,当然啦,按需配置吧



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

 

示例:

1、Bean组件类定义


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

 
 
  1. <bean class="org.springframework.validation.beanvalidation.BeanValidationPostProcessor"/>

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

 
 
  1. <bean id="user" class="com.somnus.validation.model.User">
  2. <property name="username" value="@"/>
  3. <property name="password" value="#"/>
  4. </bean>

4、测试用例

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

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


  
  
  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
  6.  
我们可以看出 用户名验证失败。

 


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

 

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

 

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


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

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

 

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


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

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


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


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

 

示例:

1、Service类定义


  
  
  1. package com.somnus.solo.validation.service;
  2.  
  3. import java.util.Collections;
  4. import java.util.List;
  5.  
  6. import javax.validation.Valid;
  7. import javax.validation.constraints.Size;
  8.  
  9. import org.springframework.validation.annotation.Validated;
  10.  
  11. import com.somnus.solo.validation.model.User;
  12.  
  13. @Validated // 告诉MethodValidationPostProcessor此Bean需要开启方法级别验证支持
  14. public class ValidationServiceImpl {
  15.  
  16. @Size(min = 1)
  17. public List<User> getUsers(@Valid User user) {
  18. return Collections.emptyList();
  19. }
  20. }
2、开启Spring3.1对方法级别验证支持(spring-config-method-validator.xml)
 
 
  1. <!--注册方法验证的后处理器-->
  2. <bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

3、测试用例


  
  
  1. package com.somnus.solo;
  2.  
  3. import java.util.Date;
  4. import java.util.Set;
  5.  
  6. import javax.validation.ConstraintViolation;
  7. import javax.validation.ConstraintViolationException;
  8.  
  9. import org.junit.Test;
  10. import org.junit.runner.RunWith;
  11. import org.springframework.test.context.ContextConfiguration;
  12. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  13.  
  14. import com.somnus.solo.support.holder.ApplicationContextHolder;
  15. import com.somnus.solo.validation.model.User;
  16. import com.somnus.solo.validation.service.ValidationServiceImpl;
  17.  
  18. @RunWith(SpringJUnit4ClassRunner.class)
  19. @ContextConfiguration(locations = "classpath:spring-validation.xml")
  20. public class ValidationSpringTest {
  21.  
  22. /**
  23. * 此方法是用来验证Spring(从3.1开始哒)对Bean Validation规范的新支持(方法级别验证)
  24. * 这里的关键是在获取异常类ConstraintViolationException,是由spring帮你做校验,如果有不符合规范的参数会抛出该异常
  25. * 然而如何让spring去做这件事情,则关键是需要加上一个BeanPostProcessor扩展点,详见配置文件
  26. */
  27. @Test
  28. public void validateParameters(){
  29. try {
  30. ValidationServiceImpl service = ApplicationContextHolder.getBean(ValidationServiceImpl.class);
  31. service.getUsers(new User("ad#min", "123456",new Date()));
  32. } catch (Throwable throwable) {
  33. System.out.println(throwable.getClass());
  34. if(throwable instanceof ConstraintViolationException){
  35. Set<ConstraintViolation<?>> constraintViolations = ((ConstraintViolationException)throwable).getConstraintViolations();
  36. for(ConstraintViolation<?> constraint:constraintViolations){
  37. System.out.println(constraint.getPropertyPath().toString());
  38. System.out.println(constraint.getMessage());
  39. System.out.println(constraint.getMessageTemplate());
  40. }
  41. }
  42. throwable.printStackTrace();
  43. }
  44. }
  45.  
  46. @Test
  47. public void validateReturnValue(){
  48. try {
  49. ValidationServiceImpl service = ApplicationContextHolder.getBean(ValidationServiceImpl.class);
  50. service.getUsers(new User("admin", "123456",new Date()));//不满足后置条件的返回值
  51. } catch (Throwable throwable) {
  52. System.out.println(throwable.getClass());
  53. if(throwable instanceof ConstraintViolationException){
  54. Set<ConstraintViolation<?>> constraintViolations = ((ConstraintViolationException)throwable).getConstraintViolations();
  55. for(ConstraintViolation<?> constraint:constraintViolations){
  56. System.out.println(constraint.getPropertyPath().toString());
  57. System.out.println(constraint.getMessage());
  58. System.out.println(constraint.getMessageTemplate());
  59. }
  60. }
  61. throwable.printStackTrace();
  62. }
  63. }
  64.  
通过如上测试,我们可以看出Spring4 已经非常好的支持契约式编程了。
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值