Spring Boot 集成 Hibernate Validation
原创2019/2/2大约 4 分钟
Spring Boot 集成 Hibernate Validation
本文介绍在 Spring Boot 中实现对请求的数据进行校验。数据校验常用到概念:
- JSR303/JSR-349: JSR303 是一项标准,只提供规范不提供实现,规定一些校验规范即校验注解,如 @Null,@NotNull,@Pattern,位于 javax.validation.constraints 包下。JSR-349 是其的升级版本,添加了一些新特性。
- hibernate validation:hibernate validation 是对这个规范的实现,并增加了一些其他校验注解,如 @Email,@Length,@Range 等等
- spring validation:spring validation 对 hibernate validation 进行了二次封装,在 springmvc 模块中添加了自动校验,并将校验信息封装进了特定的类中
1、验证环境配置
1.1、依赖
在 Spring Boot 框架下需要加入以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>1.2、配置 Bean
注意:以下配置是为了实现验证一个参数有误时后面的参数就不再验证的效果,该配置可选
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 请求参数验证
*
* @author Jastar Wang
* @date 2019-01-02
* @version 1.0
*/
@Configuration
public class ValidatorConfig {
/**
* 默认的校验模式会把所有的属性校验完成后一并返回错误信息,<br>
* 而使用Hibernate的以下实现方式则可以实现第一个错误后则不再进行后续校验
*/
@Bean
public Validator validator() {
ValidatorFactory factory = Validation.byProvider(HibernateValidator.class).configure().failFast(true)
.buildValidatorFactory();
return factory.getValidator();
}
}2、示例
2.1、定义需要校验的对象
在数据传输对象的字段上加入验证注解,如下:
public class UserDto {
@Size(min=2, max=30)
private String name;
// 自定义错误信息
@NotEmpty(message = "自定义错误信息,Email不能为空")
@Email
private String email;
@NotNull
@Min(18) @Max(100)
private Integer age;
@DateTimeFormat(pattern="MM/dd/yyyy")
@NotNull @Past
private Date birthday;
// 自定义规则注解
@PhoneValidation
private String phone;
}2.2、编写 Controller
@RequestMapping("/save")
public String save(@Valid UserDto user, BindingResult result){
if (result.hasErrors()){
List<ObjectError> errorList = result.getAllErrors();
for(ObjectError error : errorList){
System.out.println(error.getDefaultMessage());
}
}
return "success";
}注意:
此处的 @Valid 注解,使用 @Validated 注解同样可以,他们的区别如下(还有关于嵌套验证方面的区别不再赘述):
@Valid是属于javax.validation包下的原生规范,不提供分组功能;可以用在方法、构造函数、方法参数和成员属性(字段)上;@Validated是org.springframework.validation.annotation包下的(推荐),是Spring对@Valid的二次封装,提供了分组校验的功能;可以用在类型、方法和方法参数上,但是不能用在成员属性(字段)上
另:如果验证参数是 Controller 方法级别的(比如 @NotNull String name),一定要在 Controller 类上加上 @Validated 注解,否则验证不生效!!!
2.3、统一异常处理
以上 @Valid 参数后面要跟一个 BindingResult 参数,这样就可以获取到验证成功与否的信息;如果不跟这个参数,则会抛出 javax.validation.ConstraintViolationException 异常,所以此时我们就可以通过统一异常处理的方式来验证参数:
/**
* 全局请求控制处理器
*
* @author Jastar Wang
* @date 2018-12-29
* @version 1.0
*/
@Slf4j
@ControllerAdvice
public class GlobalRequestHandler {
/**
* 统一异常处理
*
* @param e 异常
* @return 结果
*/
@ResponseBody
@ExceptionHandler(value = Exception.class)
public ServiceResult<?> exceptionHandler(Exception e) {
// 参数校验
if (e instanceof ConstraintViolationException) {
ConstraintViolation<?> violation = ((ConstraintViolationException) e).getConstraintViolations().iterator()
.next();
return ServiceResult.failure(violation == null ? "参数有误" : violation.getMessage());
} else if (e instanceof MethodArgumentNotValidException) {
FieldError fieldError = ((MethodArgumentNotValidException) e).getBindingResult().getFieldError();
return ServiceResult.failure(fieldError == null ? "参数有误" : fieldError.getDefaultMessage());
}
// 其他异常
log.error(e.getMessage(), e);
return ServiceResult.failure(e.getMessage());
}
}3、自定义校验注解
3.1、@PhoneValidation
@Documented
// 指定真正实现校验规则的类
@Constraint(validatedBy = PhoneValidationValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneValidation {
String message() default "不是正确的手机号码";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@interface List {
PhoneValidation[] value();
}
}3.2、PhoneValidationValidator
public class PhoneValidationValidator implements ConstraintValidator<PhoneValidation, String> {
private static final Pattern PHONE_PATTERN = Pattern.compile(
"^((13[0-9])|(15[^4])|(18[0,2,3,5-9])|(17[0-8])|(147))\\d{8}$"
);
@Override
public void initialize(PhoneValidation constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if ( value == null || value.length() == 0 ) {
return true;
}
Matcher m = PHONE_PATTERN.matcher(value);
return m.matches();
}
}4、分组校验
4.1、声明空的接口或类
根据实际需求声明空的接口,用来标记不同的校验场景,如:
/**
* 用户注册场景
*/
public interface UserRegisterValidView {
}
/**
* 用户登录场景
*/
public interface UserLoginValidView {
}4.2、在属性校验注解中指定分组
// 若填写了groups,则该参数验证只在对应验证规则下启用
@Past(groups = { UserRegisterValidView.class }, message = "出生日期不符合要求")
private Date birthday;4.3、Controller 方法要指定场景
@RequestMapping(value = "/register", method = RequestMethod.POST)
@ResponseBody//表明对User对象的校验,启用UserRegisterValidView规则
public String register(@Validated(value = { UserRegisterValidView.class }) @RequestBody UserDto user) {
// ...
return "success";
}
