Spring4 对Bean Validation规范的新支持(方法级别验证)
大体意思是:Bean Validation 标准化了Java平台的约束定义、描述、和验证。
详细了解请参考:http://beanvalidation.org/
Bean Validation现在一个有两个规范:
定义了基于注解方式的JavaBean验证元数据模型和API,也可以通过XML进行元数据定义,但注解将覆盖XML的元数据定义。
详细了解请参考:http://jcp.org/en/jsr/detail?id=303
JSR-303主要是对JavaBean进行验证,如方法级别(方法参数/返回值)、依赖注入等的验证是没有指定的。因此又有了JSR-349规范的产生。
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包:
- <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也是可以被创建出来的,当然啦,按需配置吧
Spring4开始支持对依赖注入的依赖进行验证。Spring对依赖注入验证支持请参考《Spring开闭原则的表现-BeanPostProcessor扩展点-2》中的BeanValidationPostProcessor。
示例:
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
我们可以看出 用户名验证失败。
Spring3.1开始支持方法级别的验证。Spring对方法级别的验证支持请参考《Spring开闭原则的表现-BeanPostProcessor扩展点-2》中的MethodValidationPostProcessor。
有了方法级别验证,我们就能够更加简单的在Java世界进行契约式设计了,关于契约式设计请参考《建造无错软件:契约式设计引论》。
没有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 已经非常好的支持契约式编程了。
Spring4 对Bean Validation规范的新支持(方法级别验证)相关推荐
- JSR-303 Bean Validation 介绍及 Spring MVC 服务端验证最佳实践
任何时候,当要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情. 应用程序必须通过某种手段来确保输入参数在上下文来说是正确的. 分层的应用在很多时候,同样的数据验证逻辑会出现在不同的层, ...
- 扩展基于注解的spring缓存,使缓存有效期的设置支持方法级别-redis篇
2019独角兽企业重金招聘Python工程师标准>>> 这里用的spring对redis的封装spring-data-redis,主要是对RedisCacheManager做一个二次 ...
- Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC
http://sishuok.com/forum/blogPost/list/7798.html 在之前的<跟我学SpringMVC>中的<第七章 注解式控制器的数据验证.类型转换及 ...
- 1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知
乔丹是我听过的篮球之神,科比是我亲眼见过的篮球之神.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免 ...
- @Valid 注解详解 Java Bean Validation的前世今生
Spring @Valid 注解 校验实体属性 1 @Valid 介绍 1.1 前言 1.2 Bean Validation 1.3 关于validation包 1.3 关于Spring Hibern ...
- Java Bean Validation 详解
前言 最近维护一个老项目,项目使用最原始的Servlet,项目中充斥着各种类似判空的简单校验,为了减少重复代码,因此需要手动引入 Java 的 Bean Validation. Java Bean V ...
- JSR 303 – Bean Validation 介绍及最佳实践
关于 Bean Validation 在任何时候,当你要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情.应用程序必须通过某种手段来确保输入进来的数据从语义上来讲是正确的.在通常的情况下 ...
- JSR 303 - Bean Validation 介绍及最佳实践
关于 Bean Validation 在任何时候,当你要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情.应用程序必须通过某种手段来确保输入进来的数据从语义上来讲是正确的.在通常的情况下 ...
- 在Spring MVC应用程序中使用Bean Validation 1.1获得更好的错误消息
在许多新功能中, Bean Validation 1.1引入了使用统一表达式语言(EL)表达式的错误消息插值. 这允许基于条件逻辑来定义错误消息,还可以启用高级格式化选项 . 添加到Spring MV ...
最新文章
- linux 卸载 rtx,Ubuntu20.04系统卸载软件及清理系统垃圾缓存以及新力得
- android studio selector 插件,Android Studio 常用插件
- 怎么查询交通银行卡的支付卡号?
- PHP判断浏览器类型和浏览器语言(附各国语言简写代码)
- R语言学习笔记(四)参数估计
- java查错题(经常混淆)
- mysql日志恢复的时间格式_mysql binlog 日志恢复数据
- 【装饰者模式】Decorator Pattern
- 基于Extjs的web表单设计器
- swoft增加swagger(丝袜哥)
- 开零食店能赚钱吗?有何经营技巧?
- 手把手教你学习DSP_硬件设计
- 易语言EXUI游戏充值系统源码
- 利用excel内的doi和python批量下载外文文献
- 3dmax学习笔记 (一)
- 最新!2016中国城市GDP排名出炉
- iOS小技能: 创建渐变色背景(提供渐变色无法覆盖整个视图的解决方案)
- 高中生参加的计算机奥赛是,电脑奥赛中最寂寥的竞赛 5学生夺牌直通清华
- 两轮自平衡车系统的模型构建
- C++序列容器之 vector常见用法总结
热门文章
- MeeGo系统和SailFish系统_我是亲民_新浪博客
- Docker入门详解
- H+4.9响应式后台主题UI框架源码带完整文档-免费下载
- 前端js华为云obs上传下载文件与进度条的设置
- Win32:编译64位程序的注意点
- Unity实战 RTS3D即时战略游戏开发(三)
- 显色指数(CRI)计算软件分享(升级版本:增加同步计算R15,CCT,CIE色坐标,三刺激值等)
- linux 内存性能调优
- 快速排序(由荷兰国旗问题到快排)
- 戴尔电脑的计算机管理在哪里,戴尔电脑的设备管理器的打开方法