在开发中,往往需要对应前端穿过来的参数进行校验,有时候会显得参数校验过于臃肿,但是又不得不做。这里引入 Hibernate Validator
,它支持属性、字段、返回值、方法参数、类级别的校验,而且SpringBoot可以很轻松集成。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
配置 ValidatorFactory
。
@Configuration
public class ValidatorConfig {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}
配置校验失败异常处理。
@ControllerAdvice
public class ExceptionAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ResponseEntity<ApiResult<?>> methodArgumentNotValidException(MethodArgumentNotValidException exception) {
List<ObjectError> allErrors = exception.getBindingResult().getAllErrors();
Iterator<ObjectError> iterator = allErrors.iterator();
StringBuilder builder = new StringBuilder();
while (iterator.hasNext()) {
ObjectError error = iterator.next();
String message = error.getObjectName() + ":" + error.getDefaultMessage();
builder.append(message);
}
return new ResponseEntity<>(new ApiResult<>(HttpStatus.BAD_REQUEST.value(), builder.toString()), HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(value = ConstraintViolationException.class)
public ResponseEntity<ApiResult<?>> constraintViolationException(ConstraintViolationException exception) {
Set<ConstraintViolation<?>> constraintViolations = exception.getConstraintViolations();
Iterator<ConstraintViolation<?>> iterator = constraintViolations.iterator();
StringBuilder builder = new StringBuilder();
while (iterator.hasNext()) {
ConstraintViolation<?> constraint = iterator.next();
String message = constraint.getPropertyPath() + ":" + constraint.getMessage();
builder.append(message);
}
return new ResponseEntity<>(new ApiResult<>(HttpStatus.BAD_REQUEST.value(), builder.toString()), HttpStatus.BAD_REQUEST);
}
}
@Data
public class Car {
@NotNull
private String manufacturer;
@AssertTrue
private boolean isRegistered;
public Car(String manufacturer, boolean isRegistered) {
this.manufacturer = manufacturer;
this.isRegistered = isRegistered;
}
}
@Validated
开启参数校验,如果参数校验失败,将抛出 MethodArgumentNotValidException
异常。
@RestController
@RequestMapping("/test")
public class TestController {
@PostMapping("/car")
public void testCar(@Validated @RequestBody Car car) {
System.out.println(car);
}
}
get方法开启参数校验,要在类级别上使用 @Validated
,如果校验失败,将抛出 ConstraintViolationException
异常。
@RestController
@RequestMapping("/test")
@Validated
public class TestController {
@GetMapping("/testGet")
public void testGet(@Max(23) Integer id) {
System.out.println(id);
}
}
有时候我们需要对需要校验的字段进行分组,方便复用。这里定义一个对象 GroupDomain
,分了三个组。注意:Hibernate Validator
有个默认组的概念,如果校验限制没有被指定分组,那么属于默认分组。id
、column1
是属于默认分组(Default)。
@Data
public class GroupDomain {
@NotNull(message = "id can not null")
private Integer id;
@NotBlank(message = "column1 can not null")
private String column1;
@NotBlank(message = "column2 can not null", groups = GroupCheck2.class)
private String column2;
@NotBlank(message = "column3 can not null", groups = GroupCheck3.class)
private String column3;
@NotBlank(message = "column4 can not null", groups = GroupCheck4.class)
private String column4;
}
public interface GroupCheck2 {
}
public interface GroupCheck3 {
}
public interface GroupCheck4 {
}
@RestController
@RequestMapping("/group")
public class GroupController {
// id不能为null,column1不能为空串。其他字段不校验
@PostMapping("/defaultGroup")
public void defaultGroup(@Validated @RequestBody GroupDomain groupDomain) {
System.out.println(groupDomain);
}
// column2不能为空串。其他字段不校验
@PostMapping("/oneGroup")
public void oneGroup(@Validated(GroupCheck1.class) @RequestBody GroupDomain groupDomain) {
System.out.println(groupDomain);
}
// column3不能为空串。其他字段不校验
@PostMapping("/twoGroup")
public void twoGroup(@Validated(GroupCheck2.class) @RequestBody GroupDomain groupDomain) {
System.out.println(groupDomain);
}
// column4不能为空串。其他字段不校验
@PostMapping("/threeGroup")
public void threeGroup(@Validated(GroupCheck3.class) @RequestBody GroupDomain groupDomain) {
System.out.println(groupDomain);
}
}
当校验指定多个分组时,校验的顺序是不能确定的,我们可以指定分组校验的顺序。
@RestController
@RequestMapping("/group")
public class GroupController {
@PostMapping("/fourGroup")
public void fourGroup(@Validated(GroupChecks.class) @RequestBody GroupDomain groupDomain) {
System.out.println(groupDomain);
}
}
@GroupSequence(value = {GroupCheck4.class, GroupCheck3.class, GroupCheck2.class})
public interface GroupChecks {
}
使用 @Valid
注解可以使用级联方式校验。
public class Driver {
@Min(value = 18, message = "You have to be 18 to drive a car", groups = DriverChecks.class)
private int age;
@AssertTrue(message = "You first have to pass the driving test", groups = DriverChecks.class)
private boolean hasDrivingLicense;
// getter and setter
}
public class Car {
@NotNull
private String manufacturer;
@NotNull
@Size(min = 2, max = 14)
private String licensePlate;
@Min(2)
private int seatCount;
@AssertTrue(
message = "The car has to pass the vehicle inspection first",
groups = CarChecks.class
)
private boolean passedVehicleInspection;
@Valid
private Driver driver;
// getter and setter
}
Hibernate validator
内置很多校验注解,它也支持自定义校验注解。
自定义注解,来校验字符串是否为大小写。
public enum CaseMode {
UPPER, LOWER
}
自定义 @CheckCase
注解,有三个属性是必选要有的:message
、groups
、payload
。注解 @Constraint
来定义由谁去处理被 @CheckCase
标记的JavaBean的属性。
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
@Repeatable(CheckCase.List.class)
public @interface CheckCase {
// 这个是必选有的
String message() default "{com.example.hibernate.constraint.custom.CheckCase.message}";
// 这个是必选有的
Class<?>[] groups() default {};
// 这个是必选有的
Class<? extends Payload>[] payload() default {};
CaseMode value();
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
@interface List {
CheckCase[] value();
}
}
自定义 CheckCaseValidator
验证器,它实现了 ConstraintValidator
接口
public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
private CaseMode caseMode;
@Override
public boolean isValid(String object, ConstraintValidatorContext constraintValidatorContext) {
if (object == null) {
return true;
}
boolean isValid;
if (caseMode == CaseMode.UPPER) {
isValid = object.equals(object.toUpperCase());
} else {
isValid = object.equals(object.toLowerCase());
}
return isValid;
}
@Override
public void initialize(CheckCase constraintAnnotation) {
this.caseMode = constraintAnnotation.value();
}
}