SpringBoot Validator

穆睿才
2023-12-01

在开发中,往往需要对应前端穿过来的参数进行校验,有时候会显得参数校验过于臃肿,但是又不得不做。这里引入 Hibernate Validator ,它支持属性、字段、返回值、方法参数、类级别的校验,而且SpringBoot可以很轻松集成。

Maven

<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;
    }
}
controller编写

@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 有个默认组的概念,如果校验限制没有被指定分组,那么属于默认分组。idcolumn1 是属于默认分组(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 {
}
controller编写
@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 注解,有三个属性是必选要有的:messagegroupspayload。注解 @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

自定义 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();
    }
}

参考

 类似资料:

相关阅读

相关文章

相关问答