0


Spring Boot中参数校验

前言

为了保证数据的正确性、完整性,前后端都需要进行数据检验。作为一名后端开发工程师,不能仅仅依靠前端来校验数据,我们还需要对接口请求的参数进行后端的校验。最常见的做法就是通过if/else语句来对请求的每一个参数一一校验,当很多参数需要校验的时候,if/else语句就会比较长,写起来也比较麻烦,一点都不简洁、美观。所以,今天来和大家分享一下Spring Boot Validation。

spring-boot-starter-validation

Spring Boot 2.3 1 之后,spring-boot-starter-validation 已经不包括在了 spring-boot-starter-web 中,需要我们手动加上。

如下图所示,spring-boot-starter-web-2.2.6.RELEASE就包含了spring-boot-starter-validation,

而spring-boot-starter-web-2.5.7并没有spring-boot-starter-validation,需要自己手动加入依赖。

如下图所示,手动加入依赖spring-boot-starter-validation,实际上依赖了hibernate-validator。

@Valid和@Validated

@Valid所属包为javax.validation,不具备分组校验功能;可以用在方法、构造函数、方法参数和成员属性(field)上

说明:Java的JSR-303声明了@Valid这类接口,而hibernate-validator对其进行了实现。

JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation。

@Validated所属包为org.springframework.validation.annotation,属于spring的校验机制,具有分组校验功能;用在类型、方法和方法参数上。但不能用于成员属性(field)。

@Valid和@Validated注解可以结合使用,来实现嵌套验证。

常用的注解如下:

简单使用

添加依赖

本文使用的是Spring Boot 2.5.7,具体依赖如下:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

实体类

@Data
@ToString
public class User {

    private Integer id;

    @NotBlank(message = "姓名不能为空")
    private String name;

    @Max(value = 100, message = "最大不超过100")
    private Integer age;
}    

Controller

@RestController
public class TestController {

    /**
    * post请求 实体类User 加上注解@Valid 
    */
    @PostMapping("/test")
    public String test(@Valid @RequestBody User user) {
        System.out.println(user);
        return "Hello World";
    }

    /**
    * get请求 实体类User 加上注解@Valid 
    * 测试:http://localhost:8080/query2?name=&age=101
    */    
    @GetMapping("/query2")
    public String queryUserInfo(@Valid User user) {
        System.out.println("query2");
        return user.getName();
    }
}
/**
* 【反面教材】验证不生效
*/
@RestController
public class Test3Controller {

    @GetMapping("/test3")
    public String test3(@NotEmpty(message = "name不能为空") String name,
                        @Max(value = 100, message = "请输入数字") Integer age) {
        System.out.println("是啥啊3");
        return name;
    }

    @GetMapping("/test4")
    @Validated
    public String test4(@NotEmpty(message = "name不能为空") String name,
                        @Max(value = 100, message = "请输入数字") Integer age) {
        System.out.println("是啥啊4");
        return name;
    }

    @GetMapping("/test5")
    public String test5(@Validated @NotEmpty(message = "name不能为空") String name,
                        @Valid @Max(value = 100, message = "请输入数字") Integer age) {
        System.out.println("是啥啊5");
        return name;
    }
}
/**
* 【正面教材】类上加注解@Validated,下面的验证生效
*/
@RestController
@Validated 
public class Test2Controller {

    @GetMapping("/query")
    public String queryUserInfo(@NotEmpty(message = "name不能为空") String name,
                                @Max(value = 100, message = "请输入数字") Integer age) {
        System.out.println("是啥啊");
        return name;
    }
}

全局异常处理类

/**
* 参数校验异常包括MethodArgumentNotValidException、BindException、ConstraintViolationException
*/
@RestControllerAdvice
public class ExceptionHandler {

  @org.springframework.web.bind.annotation.ExceptionHandler(MethodArgumentNotValidException.class)
    public String handle(MethodArgumentNotValidException e) {
        e.printStackTrace();
        return e.getBindingResult().getFieldError().getDefaultMessage();
    }

    @org.springframework.web.bind.annotation.ExceptionHandler(BindException.class)
    public String handle(BindException e) {
        e.printStackTrace();
        return e.getBindingResult().getFieldError().getDefaultMessage();
    }

    @org.springframework.web.bind.annotation.ExceptionHandler(ConstraintViolationException.class)
    public String handle(ConstraintViolationException e) {
        e.printStackTrace();
        StringBuffer sb = new StringBuffer();
        for (ConstraintViolation<?> violation : e.getConstraintViolations()) {
            sb.append(violation.getMessage());
        }
        return sb.toString();
    }
}

进阶使用

分组校验

  • 约束注解上声明适用的分组信息groups
@Data
public class UserDTO {
 
    @Min(value = 10000000000000000L, groups = Update.class)
    private Long userId;
 
    @NotNull(groups = {Save.class, Update.class})
    @Length(min = 2, max = 10, groups = {Save.class, Update.class})
    private String userName;
 
    // 对于添加、修改都需要操作的公共属性,也可以不加groups标签,这时候,controller中入参需要写成@Validated({UserDTO.Update.class, Default.class}) 即可
    @NotNull(groups = {Save.class, Update.class})
    @Length(min = 6, max = 20, groups = {Save.class, Update.class})
    private String account;
 
    @NotNull(groups = {Save.class, Update.class})
    @Length(min = 6, max = 20, groups = {Save.class, Update.class})
    private String password;
 
    /**
     * 保存的时候校验分组
     */
    public interface Save {
    }
 
    /**
     * 更新的时候校验分组
     */
    public interface Update {
    }
}
  • @Validated注解上指定校验分组
@PostMapping("/save")
public Result saveUser(@RequestBody @Validated(UserDTO.Save.class) UserDTO userDTO) {
    // 校验通过,才会执行业务逻辑处理
    return Result.ok();
}
 
@PostMapping("/update")
public Result updateUser(@RequestBody @Validated(UserDTO.Update.class) UserDTO userDTO) {
    // 校验通过,才会执行业务逻辑处理
    return Result.ok();
}

嵌套校验

前面的示例中,

DTO

类里面的字段都是

基本数据类型

String

类型。但是实际场景中,有可能某个字段也是一个对象,这种情况下,可以使用

嵌套校验

比如,上面保存

User

信息的时候同时还带有

Job

信息。需要注意的是,**此时

DTO

类的对应字段必须标记

@Valid

注解**。

@Data  
public class UserDTO {  
  
    @Min(value = 10000000000000000L, groups = Update.class)  
    private Long userId;  
  
    @NotNull(groups = {Save.class, Update.class})  
    @Length(min = 2, max = 10, groups = {Save.class, Update.class})  
    private String userName;  
  
    @NotNull(groups = {Save.class, Update.class})  
    @Length(min = 6, max = 20, groups = {Save.class, Update.class})  
    private String account;  
  
    @NotNull(groups = {Save.class, Update.class})  
    @Length(min = 6, max = 20, groups = {Save.class, Update.class})  
    private String password;  
  
    // job属性上添加@Valid注解
    @NotNull(groups = {Save.class, Update.class})  
    @Valid  
    private Job job;  
  
    @Data  
    public static class Job {  
  
        @Min(value = 1, groups = Update.class)  
        private Long jobId;  
  
        @NotNull(groups = {Save.class, Update.class})  
        @Length(min = 2, max = 10, groups = {Save.class, Update.class})  
        private String jobName;  
  
        @NotNull(groups = {Save.class, Update.class})  
        @Length(min = 2, max = 10, groups = {Save.class, Update.class})  
        private String position;  
    }  
  
    public interface Save {  
    }  
  
    public interface Update {  
    }  
}

自定义校验

  • 自定义注解
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
// 指明使用那个校验器(类) 去校验使用了此标注的元素。
@Constraint(validatedBy = CaseUpperValidator.class)
@Documented
public @interface CaseUpper {

    String message() default "必须大写";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}
  • 实现ConstraintValidator接口编写约束校验器
public class CaseUpperValidator implements ConstraintValidator<CaseUpper, String> {

    // 提示信息
    private String message;

    // 实现校验逻辑的方法。value为当前需要进行校验的值,context可以给约束验证器时提供上下文数据和操作,比如设置错误信息等。
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // 为空,返回失败
        boolean isValid = false;
        // value为空不校验;另外,value都是大写校验通过
        if (Objects.isNull(value) || value.equals(value.toUpperCase())) {
            isValid = true;
        }
        // 校验不通过,实现自定义错误信息
        if (!isValid) {
            //禁止默认消息返回
            context.disableDefaultConstraintViolation();
            //自定义返回消息
            context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
        }
        return isValid;
    }

    // caseUpper为当前校验注解的实例,可以获取当前注解的属性值
    @Override
    public void initialize(CaseUpper caseUpper) {
        this.message = caseUpper.message();
    }
}
  • 使用注解@CaseUpper
@Data
@ToString
public class User {

    private Integer id;

    @NotBlank(message = "姓名不能为空")
    private String name;

    @Max(value = 100, message = "最大不超过100")
    private Integer age;

    @NotBlank(message = "englishName不能为空")
    @CaseUpper(message = "englishName必须大写")
    private String englishName;
}

参考链接:

SpringBoot 实现各种参数校验,非常实用!_枫哥和java的博客-CSDN博客

Spring Validation最佳实践及其实现原理,参数校验没那么简单! - 掘金

标签: spring boot java spring

本文转载自: https://blog.csdn.net/qq1929892209/article/details/126133350
版权归原作者 咸鱼爱吃青椒 所有, 如有侵权,请联系我们删除。

“Spring Boot中参数校验”的评论:

还没有评论