Spring4 对Bean Validation规范的新支持(方法级别验证)
2014-11-19 22:24
381 查看
大体意思是: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.1的参考实现有Hibernate Validator(下载地址:https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/);
![](https://oscdn.geek-share.com/Uploads/Images/Content/201710/ddad89f7a6740589f4c07cfc5607ea5f.jpg)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201710/3205046d7f482dac0cb46116c0e0f84d.jpg)
上图摘自hibernate validator 参考文档,从图中可以看出,我们可以在任何位置实施验证。
1、表现层验证:SpringMVC提供对JSR-349的表现层验证;
2、业务逻辑层验证:Spring3.1提供对业务逻辑层的方法验证(当然方法验证可以出现在其他层,但笔者觉得方法验证应该验证业务逻辑);
3、DAO层验证:Hibernate提供DAO层的模型数据的验证。
4、数据库端的验证:通过数据库约束来进行;
5、客户端验证支持:JSR-349也提供编程式验证支持。
对于DAO层和客户端验证支持不在我们示例范围,忽略,感兴趣的同学可以参考《hibernate validator reference》(有中文)。
在测试支持大家需要准备好如下jar包:
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.3.4</version> </dependency>
现在我们纯粹的只是利用Bean Validation和hibernate的实现Hibernate Validator来做一个校验
package com.somnus.validation.model; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; public class User { @NotNull @Pattern(regexp = "[a-zA-Z0-9_]{5,10}" , message = "{user.username.illegal}") private String username; @Size(min = 6, max=10) private String password; //省略setter/getter public User(String username, String password) { super(); this.username = username; this.password = password; } public User() { super(); } public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } }
调用 JSR 349 API 进行校验
package com.somnus.solo; import java.lang.reflect.Method; import java.text.ParseException; import java.util.Date; import java.util.Set; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.executable.ExecutableValidator; import org.apache.commons.lang3.time.DateUtils; import org.hibernate.validator.HibernateValidator; import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator; import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator; import org.junit.Test; import com.somnus.solo.validation.UserValidator; import com.somnus.solo.validation.model.User; public class ValidationTest { /** * 当前demo中所有的关于 都不再使用Bean Validation 1.0(JSR-303)旧标准, * 目前使用的是 Bean Validation 1.1(JSR-349) * * 此方法是用来学习Validator的使用 * 1、如何拿到Validator的hibernate实现 * 2、如何拿到校验失败的相关信息 * 3、User的字段tranDate上面使用了一个自定义注解(如果你需要自定义,可以参照这个) * User最终在这里是校验不通过的,因为要求了tranDate必须是大于或者今天 * @throws ParseException */ @Test public void defaultValidator() throws ParseException{ Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); User user = new User("admin","123456",DateUtils.parseDate("2014-11-11", new String[] {"yyyy-MM-dd"})); Set<ConstraintViolation<User>> violations = validator.validate(user); for(ConstraintViolation<User> data:violations){ System.out.println(data.getPropertyPath().toString() + ":" + data.getMessage()); } } /** * 此方法和是对上面一个方法的补充,上面都是用的默认值 * 而这里面拿到Validator的都是自己一个个指定相关配置, * 1、比如指定实现类HibernateValidator * 2、比如指定properties资源文件 * 3、比如指定是否返回所有校验字段的异常信息(默认返回所有) */ @Test public void hibernateValidator(){ Validator validator = Validation.byProvider(HibernateValidator.class).configure() .messageInterpolator(new ResourceBundleMessageInterpolator(new PlatformResourceBundleLocator("message/validate"))) .failFast(false) .buildValidatorFactory().getValidator(); User user = new User("adm#in","12345",new Date()); Set<ConstraintViolation<User>> violations = validator.validate(user); for(ConstraintViolation<User> data:violations){ System.out.println(data.getPropertyPath().toString() + ":" + data.getMessage()); } } /** * 此方法是用来验证Bean Validation规范的新支持(方法级别验证),这里验证方法中的参数是否符合规范 * 注意:这里需要拿到的不再是Validator,而是ExecutableValidator * 如果方法中的参数是对象model类型,记得加@Valid 注解 * @throws NoSuchMethodException */ @Test public void validateParameters() throws NoSuchMethodException{ ExecutableValidator executableValidator = Validation.byProvider(HibernateValidator.class).configure() .messageInterpolator(new ResourceBundleMessageInterpolator(new PlatformResourceBundleLocator("message/validate"))) .failFast(false) .buildValidatorFactory().getValidator().forExecutables(); UserValidator object = new UserValidator(); Method method = object.getClass().getMethod( "verify", new Class[]{User.class} ); Object[] parameterValues = {new User("adm#in","12345",new Date())}; Set<ConstraintViolation<UserValidator>> violations = executableValidator.validateParameters( object, method, parameterValues ); for(ConstraintViolation<UserValidator> data:violations){ System.out.println(data.getPropertyPath().toString() + ":" + data.getMessage()); } } /** * 此方法是用来验证Bean Validation规范的新支持(方法级别验证),这里验证方法中的返回值是否符合规范 * 这些写法在硬编码这里略显笨拙,但是一旦和切面一起使用将是一把利器,再也不用傻傻的在每个方法中去做校验了 * 本项目已经做了相关示例,详细请见src/main/java中的【com.somnus.solo.support.aspect.ValidationAspect】 * @throws NoSuchMethodException */ @Test public void validateReturnValue() throws NoSuchMethodException{ ExecutableValidator executableValidator = Validation.buildDefaultValidatorFactory().getValidator().forExecutables(); UserValidator object = new UserValidator(); Method method = object.getClass().getMethod( "getUsers", new Class[]{} ); Object returnValue = object.getUsers(); Set<ConstraintViolation<UserValidator>> violations = executableValidator.validateReturnValue( object, method, returnValue ); for(ConstraintViolation<UserValidator> data:violations){ System.out.println(data.getPropertyPath().toString() + ":" + data.getMessage()); } } }
针对上述我给出的两组测试,区别只在于如何获得Validator,从代码上看去差别挺大,其实本质上没有任何区别,都是获得ValidationImpl
/** * The main Bean Validation class. This is the core processing class of Hibernate Validator. * * @author Emmanuel Bernard * @author Hardy Ferentschik * @author Gunnar Morling * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI * @author Guillaume Smet */ public class ValidatorImpl implements Validator, ExecutableValidator {
我们可以通过看源码的方式,来了解其中的区别。
这里之所以要讲清楚为什么有这两种方式,其实也是为Spring框架对如果引入Validator做铺垫,具体详情请看下文,并且找到相关bean的源码
我们如果不给这两个bean手动注入Validator,它也可以拿到hibernate提供的ValidatorImpl
另外我们通常也会在Spring项目中碰到有开发者会使用
然后就有了如下配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="validatemessageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="classpath:message/validate"/> <property name="fileEncodings" value="utf-8"/> <property name="cacheSeconds" value="120"/> </bean> <bean name="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <!-- 不设置则默认去找org.hibernate.validator.HibernateValidator--> <property name="providerClass" value="org.hibernate.validator.HibernateValidator" /> <!--不设置则默认为classpath下的 ValidationMessages.properties --> <property name="validationMessageSource" ref="validatemessageSource"/> <!-- 不设置则默认为false,true和false的区别在于:如果为true则不管验证项有多少个为失败的, 都只返回解析到的第一个,其余再返回,如果为false则返回所有验证失败项 --> <property name="validationPropertyMap"> <map> <entry key="hibernate.validator.fail_fast" value="true"/> </map> </property> </bean> </beans>
通过我写的注释,想必你已明白,貌似相关注入就算我全部不写这个Validator也是可以被创建出来的,当然啦,按需配置吧
示例:
1、Bean组件类定义
2、开启依赖注入验证支持(spring-config-bean-validator.xml)
<bean class="org.springframework.validation.beanvalidation.BeanValidationPostProcessor"/>
3、Bean的XML配置定义(spring-config-bean-validator.xml)
<bean id="user" class="com.somnus.validation.model.User"> <property name="username" value="@"/> <property name="password" value="#"/> </bean>
4、测试用例:
@RunWith(value = SpringJUnit4ClassRunner.class) @ContextConfiguration(value = {"classpath:spring-config-bean-validator.xml"}) public class BeanValidatorTest { @Autowired User user; @Test public void test() { } }
5、运行测试后,容器启动失败并将看到如下异常:
java.lang.IllegalStateException: Failed to load ApplicationContext …… 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 …… Caused by: org.springframework.beans.factory.BeanInitializationException: Bean state is invalid: password - password.length.illegal; username - user.username.illegal
我们可以看出 用户名验证失败。
没有MethodValidationPostProcessor之前我们可能这样验证:
public User get(Integer uuid) { //前置条件 Assert.notNull(uuid); Assert.isTrue(uuid > 0, "uuid must lt 0"); //获取 User Model Userl user = new User(); //此处应该从数据库获取 //后置条件 Assert.notNull(user); return user; }
前置条件和后置条件的书写是很烦人的工作。
有了MethodValidationPostProcessor之后我们可以这样验证:
public @NotNull User get(@NotNull @Size(min = 1) Integer uuid) { //获取 User Model User user = new User(); //此处应该从数据库获取 return user; }
前置条件的验证:在方法的参数上通过Bean Validation注解进行实施
后置条件的验证:直接在返回值上通过Bean Validation注解进行实施。
非常好,非常好,自此我们可以在Java世界进行更完美的契约式编程了。
示例:
1、Service类定义
package com.somnus.solo.validation.service; import java.util.Collections; import java.util.List; import javax.validation.Valid; import javax.validation.constraints.Size; import org.springframework.validation.annotation.Validated; import com.somnus.solo.validation.model.User; @Validated // 告诉MethodValidationPostProcessor此Bean需要开启方法级别验证支持 public class ValidationServiceImpl { @Size(min = 1) public List<User> getUsers(@Valid User user) { return Collections.emptyList(); } }
2、开启Spring3.1对方法级别验证支持(spring-config-method-validator.xml)
<!--注册方法验证的后处理器--> <bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
3、测试用例
package com.somnus.solo; import java.util.Date; import java.util.Set; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.somnus.solo.support.holder.ApplicationContextHolder; import com.somnus.solo.validation.model.User; import com.somnus.solo.validation.service.ValidationServiceImpl; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring-validation.xml") public class ValidationSpringTest { /** * 此方法是用来验证Spring(从3.1开始哒)对Bean Validation规范的新支持(方法级别验证) * 这里的关键是在获取异常类ConstraintViolationException,是由spring帮你做校验,如果有不符合规范的参数会抛出该异常 * 然而如何让spring去做这件事情,则关键是需要加上一个BeanPostProcessor扩展点,详见配置文件 */ @Test public void validateParameters(){ try { ValidationServiceImpl service = ApplicationContextHolder.getBean(ValidationServiceImpl.class); service.getUsers(new User("ad#min", "123456",new Date())); } catch (Throwable throwable) { System.out.println(throwable.getClass()); if(throwable instanceof ConstraintViolationException){ Set<ConstraintViolation<?>> constraintViolations = ((ConstraintViolationException)throwable).getConstraintViolations(); for(ConstraintViolation<?> constraint:constraintViolations){ System.out.println(constraint.getPropertyPath().toString()); System.out.println(constraint.getMessage()); System.out.println(constraint.getMessageTemplate()); } } throwable.printStackTrace(); } } @Test public void validateReturnValue(){ try { ValidationServiceImpl service = ApplicationContextHolder.getBean(ValidationServiceImpl.class); service.getUsers(new User("admin", "123456",new Date()));//不满足后置条件的返回值 } catch (Throwable throwable) { System.out.println(throwable.getClass()); if(throwable instanceof ConstraintViolationException){ Set<ConstraintViolation<?>> constraintViolations = ((ConstraintViolationException)throwable).getConstraintViolations(); for(ConstraintViolation<?> constraint:constraintViolations){ System.out.println(constraint.getPropertyPath().toString()); System.out.println(constraint.getMessage()); System.out.println(constraint.getMessageTemplate()); } } throwable.printStackTrace(); } }
通过如上测试,我们可以看出Spring4 已经非常好的支持契约式编程了。
相关文章推荐
- Spring3.1 对Bean Validation规范的新支持(方法级别验证)
- Spring4 对Bean Validation规范的新支持(方法级别验证)
- Spring3.1 对Bean Validation规范的新支持(方法级别验证)
- Spring3.1 对Bean Validation规范的新支持(方法级别验证)
- Spring3.1 对Bean Validation规范的新支持(方法级别验证)
- Spring3.1 对Bean Validation规范的新支持(方法级别验证)
- 远程计算机需要网络级别身份验证,而您的计算机不支持该验证 解决方法
- Spring方法级别的验证
- 解决方法:远程计算机需要网络级别身份验证,而您的计算机不支持该验证
- 解决方法:远程计算机需要网络级别身份验证,而您的计算机不支持该验证
- 远程计算机需要网络级别身份验证,而您的计算机不支持该验证的解决方法
- 远程计算机需要网络级别身份验证,而您的计算机不支持该验证的解决方法
- IIS 指定了身份验证方案“IntegratedWindowsAuthentication, Anonymous”,但绑定仅支持一种身份验证的规范。有效的身份验证方案为摘要、协商、NTLM、基本或匿名。解决方法
- Spring验证规则使用方法(整理与修改)
- 远程计算机需要网络级别身份验证,而您的计算机不支持该验证,请联系您的系统管理员或者技术人员来获得帮助
- 让XP远程支持网络身份验证,解决“远程计算机需要网络级别身份验证,你的系统不支持”难题
- 解决“远程计算机需要网络级别身份验证,你的系统不支持”难题
- 为XP远程增加网络身份验证,解决“远程计算机需要网络级别身份认证 您的计算机不支持该验证”问题
- Win2008 远程计算机需要网络级别身份验证,而您的计算机不支持该验证
- 让XP SP3远程桌面支持“网络级别身份验证”