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

来源:互联网 发布:怎么可以增加淘宝店的 编辑:程序博客网 时间:2024/06/05 08:07


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 已经非常好的支持契约式编程了。
阅读全文
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 摩托车的手续都怎么办 摩托车罚单掉了怎么办 行人遇到黄灯该怎么办 长辈借钱不还怎么办 不绣刚电梯轿壁有凹槽怎么办 电梯下限位故障怎么办 卫生间夏天太热怎么办 07大檐帽变形了怎么办 税务局不批发票怎么办 进项发票太多了怎么办 发票报销联丢失怎么办 发票领用簿没有怎么办 发票购买本遗失怎么办 销售方遗失发票怎么办 增值税发票发票联丢失怎么办 苹果购买发票丢失怎么办 空白增值税发票发票丢失怎么办 网购发票 领购簿怎么办 购物发票丢了怎么办 饭店客人买单要少钱怎么办 发票备注栏写错怎么办 卖房子发票丢失怎么办 发票二维码蓝票怎么办 车祸伤者出院怎么办 微信付款失败怎么办 增值税电子发票没打税号怎么办 买假出租车发票怎么办 纳税号错了怎么办 发票抬头写错怎么办 增值税发票打错顺序怎么办 发票打错了怎么办 电子发票错了怎么办 税率开高了怎么办 增值发票折叠了怎么办 播放器格式不对怎么办 发票弄上油了怎么办 快手视频快进了怎么办? 三星手机没声音怎么办 mp4不是标准格式怎么办 苹果七充电慢怎么办 迅捷转换器转换失败怎么办