Spring Boot与Spring Mvc集成Hibernate Validator参数校验
- Hibernate Validator概述
- Hibernate Validator常用注解
- 使用Validator参数校验
- Spring Boot集成Hibernate Validator
- 快速失败返回模式
- Spring Mvc集成Hibernate Validator
Hibernate Validator概述
Hibernate Validator框架可以很优雅的方式实现参数的校验,让业务代码和校验逻辑分开,不再编写重复的校验逻辑。
hibernate-validator的maven坐标
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.4.Final</version>
</dependency>
Hibernate Validator常用注解
hibernate-validator提供的校验方式为在类的属性上加入相应的注解来达到校验的目的。所有的注解都位于javax.validation.constraints和org.hibernate.validator.constraints包中。
| 注解 | 说明 |
|---|---|
| @Null | 只能为null |
| @NotNull | 必须不为null |
| @Digits(integer,fraction) | 限制必须为一个小数,且整数部分的位数不能超过integer, 小数部分的位数不能超过fraction |
| @Size(max,min) | 限制字符长度必须在min到max之间 |
| @SafeHtml | 字符串是安全的html |
| @AssertTrue | 用于boolean字段,该字段只能为true |
| @AssertFalse | 用于boolean字段,该字段只能为false |
| @CreditCardNumber | 对信用卡号进行一个大致的验证 |
| @DecimalMax | 只能小于或等于该值 |
| @DecimalMin | 只能大于或等于该值 |
| 检查是否是一个有效的email地址 | |
| @Future | 检查该字段的日期是否是属于将来的日期 |
| @Past | 限制必须是一个过去的日期 |
| @Length(min=,max=) | 检查所属的字段的长度是否在min和max之间,只能用于字符串 |
| @Max | 该字段的值只能小于或等于该值 |
| @Min | 该字段的值只能大于或等于该值 |
| @NotNull | 不能为null |
| @NotBlank | 不能为空,检查时会将空格忽略 |
| @NotEmpty | 不能为空,这里的空是指空字符串 |
| @Pattern(regex=) | 被注释的元素必须符合指定的正则表达式 |
| @URL(protocol=,host,port) | 检查是否是一个有效的URL,如果提供了protocol,host等,则该URL还需满足提供的条件 |
使用Validator参数校验
Spring Boot集成Hibernate Validator
添加依赖
<dependencies>
<!--spring-boot-starter-web中已经依赖了hibernate-validator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring-boot-starter-web会通过maven的依赖传递特性传递了hibernate-validator的maven坐标
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.4.Final</version>
</dependency>
-->
</dependencies>
简单数据类型校验
在类上使用@Validated注解标注
name参数使用@NotBlank标注,表示不能为空,提示信息为{required}占位符里的内容
email参数使用@Email注解标注,表示必须为一个合法的邮箱值(可以为空),提示信息为{invalid}占位符里的内容
name参数使用@Size(max,min)标注,表示限制字符长度必须在min到max之间,提示信息为{required}占位符里的内容
@RestController
@Validated //开启校验功能
public class TestController {
@GetMapping("test")
public String test(@NotBlank(message = "不能为空") String username, @Size(min = 6,max = 15) String password, @Email(message = "{user.email}") String email) {
return "success";
}
}
定义占位符的内容
message属性用于添加国际化支持,创建名为ValidationMessages.properties的文件(默认名称,不区分大小写,放在src/main/resources路径下)
ValidationMessages.properties文件中每条信息的key值对应于注解中message属性占位符的值。
在resources目录下新建ValidationMessages.properties文件
user.email=\u683c\u5f0f\u4e0d\u5408\u6cd5
内容为中文转Unicode后的值,可以使用http://tool.chinaz.com/tools/unicode.aspx网站转换
测试简单参数校验
访问http://localhost:8888/test?username=&password=123&email=123
参数校验不通过时,会抛出javax.validation.ConstraintViolationException
javax.validation.ConstraintViolationException: test.username: 不能为空, test.password: 个数必须在6和15之间, test.email: 格式不合法
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:116) ~[spring-context-5.2.12.RELEASE.jar:5.2.12.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.12.RELEASE.jar:5.2.12.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.2.12.RELEASE.jar:5.2.12.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691) ~[spring-aop-5.2.12.RELEASE.jar:5.2.12.RELEASE]
对象属性校验
新建User类
public class User {
@NotBlank(message = "不能为空")
private String username;
@Size(min = 6,max = 15)
private String password;
@Email(message = "{user.email}")
private String email;
}
使用实体对象传参的方式参数校验需要在相应的参数前加上@Valid注解
@RestController
@Validated
public class TestController {
@GetMapping("test")
public String test((@Valid User user) {
return "success";
}
}
参数校验不通过时,会抛出org.springframework.validation.BindException异常
Field error in object 'user' on field 'password': rejected value [123]; codes [Size.user.password,Size.password,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.password,password]; arguments []; default message [password],15,6]; default message [个数必须在6和15之间]
Field error in object 'user' on field 'username': rejected value []; codes [NotBlank.user.username,NotBlank.username,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.username,username]; arguments []; default message [username]]; default message [不能为空]
Field error in object 'user' on field 'email': rejected value [123]; codes [Email.user.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@10f84cd,.*]; default message [格式不合法]]
异常处理
使用全局异常捕获来处理这种异常
@RestControllerAdvice
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler {
/**
* 方法参数校验异常拦截处理
*
* @param e
* @return
*/
@ExceptionHandler(value = ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleConstraintViolationException(ConstraintViolationException e) {
StringBuilder message = new StringBuilder();
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
for (ConstraintViolation<?> violation : violations) {
Path path = violation.getPropertyPath();
String[] split = path.toString().split("\\.");
message.append(split[1]).append(violation.getMessage()).append(",");
}
message = new StringBuilder(message.substring(0, message.length() - 1));
return message.toString();
}
/**
* 实体对象传参校验异常拦截处理
*
* @param e
* @return
*/
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String validExceptionHandler(BindException e) {
StringBuilder message = new StringBuilder();
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
for (FieldError error : fieldErrors) {
message.append(error.getField()).append(error.getDefaultMessage()).append(",");
}
message = new StringBuilder(message.substring(0, message.length() - 1));
return message.toString();
}
}
快速失败返回模式
校验框架会对多个属性都进行数据校验(默认行为),如果希望只要有一个属性校验失败就直接返回提示信息,后面的属性不再进行校验,这是就需要指定校验时使用快速失败返回模式
定义校验时使用快速失败返回模式
创建ValidatorConfiguration类,指定校验时使用快速失败返回模式
public class ValidatorConfiguration {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory =
Validation.byProvider(HibernateValidator.class)
.configure()
//快速失败返回模式
.addProperty("hibernate.validator.fail_fast", "true")
.buildValidatorFactory();
return validatorFactory.getValidator();
}
/**
* 开启快速返回
* 如果参数校验有异常,直接抛异常,不会进入到controller,使用全局异常拦截进行拦截
*/
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
//设置validator模式为快速失败返回
postProcessor.setValidator(validator());
return postProcessor;
}
}
定义控制快速失败返回模式注解
创建注解EnableFormValidator用于控制快速失败返回模式的开启
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ValidatorConfiguration.class)
public @interface EnableFormValidator {
}
开启快速失败返回模式
在启动类上加入EnableFormValidator注解,开启快速失败返回模式
@SpringBootApplication
@EnableFormValidator
public class App {
public static void main(String[] args) {
SpringApplication.run(App .class,args);
}
}
测试快速失败返回模式
当输入的数据有多个都不符合校验规则,但是只有一个校验失败异常信息,说明已经开启了快速失败返回模式。
Spring Mvc集成Hibernate Validator
添加依赖
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.4.Final</version>
</dependency>
配置spring-mvc.xml
<!-- Bean级别的校验,方法中的参数Bean必须添加@Valid注解,后面紧跟着BindingResult result参数-->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator" />
</bean>
<!-- 方法级别的校验,校验方法所在类必须添加@Validated注解-->
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
<!-- 可以引用自己的 validator 配置,在本文中(下面)可以找到 validator 的参考配置,如果不指定则系统使用默认的 -->
<property name="validator" ref="validator" />
</bean>
使用校验注解
/**
* 详细地址
*/
@Size(max = 200,message = "详细地址不可超过200个字符")
private String address;
/**
* 事由
*/
@Size(max = 350,message = "事由不可超过350个字符")
private String reason;
/**
* 处理结果
*/
@Size(max = 350,message = "处理结果不可超过350个字符")
private String results;
}
@GetMapping("/getDamageRegistrationList")
public HashMap<String,Object> getDamageRegistrationList(@NotBlank(message = "车牌号不能为空") String carNumber){
}
配置全局异常处理
/**
* 全局参数校验异常处理
*/
@RestControllerAdvice
@Log4j
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class GlobalViolationException {
/**
* 方法参数校验异常拦截处理
*
* @param e
* @return
*/
@ExceptionHandler(value = ConstraintViolationException.class)
@ResponseStatus(HttpStatus.OK)
public ModelAndView handleConstraintViolationException(ConstraintViolationException e) {
String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(","));
ModelAndView model = new ModelAndView();
Map<String, Object> attributes = new HashMap<String, Object>();
FastJsonJsonView view = new FastJsonJsonView();
log.error("=======GlobalViolationException start=======");
log.error("方法参数校验异常拦截处理:" + message);
attributes.put("global", true);
attributes.put("code", 0);
attributes.put("dialog", false);
attributes.put("msg", message);
view.setAttributesMap(attributes);
model.setView(view);
log.error("=======GlobalViolationException end=======");
return model;
}
/**
* 实体对象传参校验异常拦截处理
*
* @param e
* @return
*/
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.OK)
public ModelAndView validExceptionHandler(BindException e) {
String message = e.getBindingResult().getFieldErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(","));
ModelAndView model = new ModelAndView();
Map<String, Object> attributes = new HashMap<String, Object>();
FastJsonJsonView view = new FastJsonJsonView();
log.error("=======GlobalViolationException start=======");
log.error("对象参校验异常拦截处理:" + message);
attributes.put("global", true);
attributes.put("code", 0);
attributes.put("dialog", false);
attributes.put("msg", message);
view.setAttributesMap(attributes);
model.setView(view);
log.error("=======GlobalViolationException end=======");
return model;
}
}
执行测试
code: 0
dialog: false
global: true
msg: "详细地址不可超过200个字符,处理结果不可超过350个字符"