SpringBoot使用validator进行参数校验

  1. @Validated、@Valid和BindingResult

1、Bean Validation

Bean Validation是Java定义的一套基于注解的数据校验规范,比如@Null、@NotNull、@Pattern等,它们位于 javax.validation.constraints这个包下。

目前已经从JSR 303的1.0版本升级到JSR 349的1.1版本,再到JSR 380的2.0版本。

JSR303 api文档:Future (Java EE 6 )

JSR349 api文档:Bean Validation API 1.1.0.Final

JSR380 api文档:Jakarta Bean Validation API 2.0.2

2、hibernate validator

hibernate validator是对这个规范的实现,并增加了一些其他校验注解,如 @NotBlank、@NotEmpty、@Length等,它们位于org.hibernate.validator.constraints这个包下。

3、兼容性表格

Bean Validation

Hibernate Validation

JDK

Spring Boot

1.1

5.4 +

6+

1.5.x

2.0

6.0 +

8+

2.0.x

4、Bean Validation 2.0新特性

1、支持容器的校验,通过TYPE_USE类型的注解实现对容器内容的约束:List<@Email String>

2、拓展元数据(新增注解):@Email,@NotEmpty,@NotBlank,@Positive,@PositiveOrZero,@Negative,@NegativeOrZero,@PastOrPresent和@FutureOrPresent(像@Email、@NotEmpty、@NotBlank之前是Hibernate额外提供的,2.0标准后hibernate标注为过期)

它唯一实现为Hibernate Validator。对于Hibernate Validator也扩展了一些注解支持。

依赖

hibernate validator框架已经集成在 spring-boot-starter-web中,所以无需再添加其他依赖。如果不是Spring Boot项目,需要添加如下依赖。

@Valid和@Validated 区别

Spring Validation验证框架对参数的验证机制提供了@Validated(Spring's JSR-303规范,是标准JSR-303的一个变种)。

javax提供了@Valid,配合BindingResult可以直接提供参数验证结果(标准JSR-303规范)。

@Validated对@Valid进行了二次封装,在使用上并没有区别,但在分组、注解位置、嵌套验证等功能上有所不同
  • 分组

@Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制。

@Valid:没有分组校验的功能。

  • 注解地方

@Validated:用在类型、方法和方法参数上(类, 方法, 参数)。但不能用于成员属性。

@Valid:可以用在方法、构造函数、方法参数和成员属性上(方法, 构造器, 参数,字段, 泛型),可以用@Valid实现嵌套验证

两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能
如A类中引用B类,且A、B二类都有内部校验,为了使B类也生效,在A类中引用B类时,在B类变量上加@Valid注解,如果B类为集合等类型且不能为空还需要再加@NotEmpty。

BindingResult

BindingResult用在实体类校验信息返回结果绑定。

该类作为方法入参,要写在实体对象后面。


@PostMapping("/menus")
public Result addMenu(@RequestBody @Valid Menu menu, BindingResult result) {}
  1. 规则注解

Bean validator内置注解

hibernate validator扩展注解

分类

空与非空

注解

支持Java类型

说明

@Null

Object

为null

@NotNull

Object

不为null

@NotBlank

CharSequence

不为null,且必须有一个非空格字符

@NotEmpty

CharSequence、Collection、Map、Array

不为null,且不为空(length/size>0)

Boolean

注解

支持Java类型

说明

备注

@AssertTrue

boolean、Boolean

为true

为null有效

@AssertFalse

boolean、Boolean

为false

为null有效

日期

注解

支持Java类型

说明

备注

@Future

Date、

Calendar、

Instant、

LocalDate、

LocalDateTime、

LocalTime、

MonthDay、

OffsetDateTime、

OffsetTime、

Year、

YearMonth、

ZonedDateTime、

HijrahDate、

JapaneseDate、

MinguoDate、

ThaiBuddhistDate

验证日期为当前时间之后

为null有效

@FutureOrPresent

Date、

Calendar、

Instant、

LocalDate、

LocalDateTime、

LocalTime、

MonthDay、

OffsetDateTime、

OffsetTime、

Year、

YearMonth、

ZonedDateTime、

HijrahDate、

JapaneseDate、

MinguoDate、

ThaiBuddhistDate

验证日期为当前时间或之后

为null有效

@Past

Date、

Calendar、

Instant、

LocalDate、

LocalDateTime、

LocalTime、

MonthDay、

OffsetDateTime、

OffsetTime、

Year、

YearMonth、

ZonedDateTime、

HijrahDate、

JapaneseDate、

MinguoDate、

ThaiBuddhistDate

验证日期为当前时间之前

为null有效

@PastOrPresent

Date、

Calendar、

Instant、

LocalDate、

LocalDateTime、

LocalTime、

MonthDay、

OffsetDateTime、

OffsetTime、

Year、

YearMonth、

ZonedDateTime、

HijrahDate、

JapaneseDate、

MinguoDate、

ThaiBuddhistDate

验证日期为当前时间或之前

为null有效

数值

注解

支持Java类型

说明

备注

@Max

BigDecimal、BigInteger,

byte、short、int、long以及包装类

小于或等于

为null有效

@Min

BigDecimal、BigInteger,

byte、short、int、long以及包装类

大于或等于

为null有效

@DecimalMax

BigDecimalBigInteger、CharSequence,

byte、short、int、long以及包装类

小于或等于

为null有效

@DecimalMin

BigDecimal、BigIntegerCharSequence,

byte、short、int、long以及包装类

大于或等于

为null有效

@Negative

BigDecimal、BigInteger,

byte、short、int、long、float、double以及包装类

负数

为null有效,0无效

@NegativeOrZero

BigDecimal、BigInteger,

byte、short、int、long、float、double以及包装类

负数或零

为null有效

@Positive

BigDecimal、BigInteger,

byte、short、int、long、float、double以及包装类

正数

为null有效,0无效

@PositiveOrZero

BigDecimal、BigInteger,

byte、short、int、long、float、double以及包装类

正数或零

为null有效

@Digits(integer = 3, fraction = 2)

BigDecimal、BigInteger、CharSequence,

byte、short、int、long以及包装类

整数位数和小数位数上限

为null有效

@Length

String

字符串长度范围

@Length

@Range

数值类型和String

指定范围

@Range

其他

注解

支持Java类型

说明

备注

@Pattern

CharSequence

匹配指定的正则表达式

为null有效

@Email

CharSequence

邮箱地址

为null有效,默认正则 '.*'

@Size

CharSequence、Collection、Map、Array

大小范围(length/size>0)

为null有效

@URL

URL地址验证

@URL

  1. 使用

单参数校验

需要在参数前添加注解,而且controller类上必须添加@Validated注解。


@RestController
@RequestMapping("/menu")
@Validated // 单参数校验需要加的注解
public class SysMenuController {
    @DeleteMapping("/menus")
    public Result deleteMenu(@NotNull(message = "id不能为空") Long id) {
    }
}

对象参数校验

先在对象的校验属性上添加注解,然后在Controller方法的对象参数前添加@Valid、@Validated


// 对象
public class Menu {
    private Long menuId;
    @NotNull(message =parentId不能为空")
    private Long parentId;
}

@PostMapping("/menus")
public Result addMenu(@RequestBody @Valid Menu menu, BindingResult result) {
}

对象嵌套


// 对象
public class PagedQueryReqBody<T> {
    private Integer page_no;
    private Integer page_row_no;
    @NotNull
    private String page_flg;
    @Valid
    private T data_request;
}

public class DataReqPQ {
    @NotNull
    private String car_no;
}

// 接口
@PostMapping(value = "/queryParameter")
public Result queryParameter(@RequestBody @Validated PagedQueryReqBody<DataReqPQ> requestMsg, BindingResult result){
}

配置validator匹配到一个错误时立即返回


@Configuration
public class ValidatorConfig {
    // 遇到第一个错误后立即返回,而不是遍历完全部错误
    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                .addProperty("hibernate.validator.fail_fast", "true") //快速验证模式,有第一个参数不满足条件直接返回
                .buildValidatorFactory();
        return validatorFactory.getValidator();
    }

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
        postProcessor.setValidator(validator());
        return postProcessor;
    }
}

自定义注解

@Target
用于描述注解的适用范围(被描述的注解可以用在什么地方)
ElementType.TYPE:接口(包括注解)、类、枚举
ElementType.FIELD:字段、枚举的常量
ElementType.METHOD:方法
ElementType.PARAMETER:方法参数
ElementType.CONSTRUCTOR:构造函数
ElementType.LOCAL_VARIABLE:局部变量
ElementType.ANNOTATION_TYPE:注解类型
ElementType.PACKAGE:包
@Retention
表示被描述的注解在什么范围内有效
RetentionPoicy.SOURCE:源码级别保留,编译后即丢弃。
RetentionPoicy.CLASS:编译级别保留,编译后的class文件中存在,在jvm运行时丢弃,这是默认值。
RetentionPoicy.RUNTIME:运行级别保留,编译后的class文件中存在,在jvm运行时保留,可以被反射调用。
生命周期长度 SOURCE < CLASS < RUNTIME
@Constraint
用于处理验证逻辑,根据根据自己的业务需求来完成验证的逻辑。
@Documented
可以被例如javadoc此类的工具文档化

单字段注解

示例:验证版本号是否符合系统定义

自定义注解


@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER,ElementType.FIELD})
@Constraint(validatedBy = VersionValidator.class)
public @interface VersionValid {
    // 自行定义的值
    String[] values();
    // 错误信息,如果我们没有在注解中定义错误信息的话,他会默认去寻找com.atguigu.common.valid.ListValue.message为key的错误信息
    String message() default "版本号无效";
    // 分组验证
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

自定义检查逻辑

ConstraintValidator接口:有两个泛型,第一个是自定义的注解类,第二个就是要验证的数据的类型


public class VersionValidator implements ConstraintValidator<VersionValid,Object> {

    private String[] values;
    // 初始化方法
    @Override
    public void initialize(VersionValid versionValidator) {
        this.values = versionValidator.values();
    }
    // 验证的逻辑方法,返回true,则验证通过,否则则不通过。
    @Override
    public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
        Boolean isFlag = false;
        for (int i = 0; i < values.length; i++){
            // 存在一致就跳出循环
            if (values[i] .equals(value)){
                isFlag = true; 
                break;
            }
        }
        return isFlag;
    }
}

实体类


 @NotNull
 @InValues(values = {"001","002"})
 private String version;

多字段校验

示例:两次输入密码一致性校验

自定义注解


//用来验证类中多个字段的validator的注解
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PassValidator.class)
@Documented
public @interface PassValid {
        // 报错信息
        String message() default "confirmPassword:两次输入密码需一致";
        Class<?>[] groups() default {};
        Class<? extends Payload>[] payload() default {};
          //密码字段
          String password();
          //确认密码字段
          String password_confirm();
}

自定义检查逻辑


public class PassValidator implements ConstraintValidator<PassValid, Object> {
     //密码
     private String passFieldName;
     //确认密码
     private String confirmFieldName;

     @Override
     public void initialize(final PassValid constraintAnnotation) {
         passFieldName = constraintAnnotation.password();
         confirmFieldName = constraintAnnotation.password_confirm();
     }

     @Override
     public boolean isValid(final Object src, final ConstraintValidatorContext context) {
         BeanWrapperImpl wrapper = new BeanWrapperImpl(src);
         String passObj = (String)wrapper.getPropertyValue(passFieldName);
         String confirmObj = (String)wrapper.getPropertyValue(confirmFieldName);
         return passObj != null && passObj.equals(confirmObj);
     }
 }

实体类


@Data
@NoArgsConstructor
@AllArgsConstructor
@PassValid(password = "password", password_confirm = "confirmPassword")
public class UserVo {
    private String name;
    @NotBlank(message = "密码不能为空")
    private String password;
    @NotBlank(message = "确认密码不能为空")
    private String confirmPassword;
}

分组校验

新建组

Validated有自己默认的组 Default.class


public interface Update {
}
public interface Add  extends Default {
}

// 对象
public class User {
    @NotBlank(message = "id不能为空",groups = {Update.class})
    private String id;
    private String name;
    @NotBlank(message = "密码不能为空",groups = {Add.class})
    private String password;
}
id属性的校验属于Update分组的校验
password属性的校验属于Add、Default分组的校验

使用分组

使用默认分组:Add分组继承Default,所以校验password,不校验id


@PostMapping("/addUser")
public Resp addUser(@Validated @RequestBody User uer) {
}

使用Update分组:只校验id,不校验password


@PostMapping("/updateUser")
public Resp updateUser(@Validated(Update.class) @RequestBody User user) {
}

同一字段使用多个校验

使用:注解.List


public class User {
    private String id;
    private String name;
    private String password;
    @Pattern.List(value = {
        @Pattern(regexp = "0", message = "锁定的用户不能修改", groups = {Update.class}),
        @Pattern(regexp = "1", message = "删除状态错误", groups = {Delete.class})
    })
    private String status;
}
  1. 异常处理

全局异常处理类

缺少参数抛出的异常是MissingServletRequestParameterException

单参数校验失败后抛出的异常是ConstraintViolationException

get请求的对象参数校验失败后抛出的异常是BindException

post请求的对象参数校验失败后抛出的异常是MethodArgumentNotValidException

不同异常对象的结构不同,对异常消息的提取方式也就不同。

ConstraintViolationException:单个参数校验失败(后端实际接收的一个字段)

BindException:表单对象参数违反约束,仅对于表单提交有效(接收参数没有加@RequestBody注解),对于以json格式提交将会失效

MethodArgumentNotValidException:JSON请求参数违反约束,为json格式有效(接收参数加上@RequestBody注解)

MissingServletRequestParameterException:参数缺失

MethodArgumentTypeMismatchException:请求参数的类型与处理器方法参数类型不匹配

HttpMessageNotReadableException:请求体为空、无效的JSON格式、无法将JSON转换为目标对象


@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)// 设置状态码为500
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public String postExceptionHandler(MethodArgumentNotValidException e){
        log.error("执行异常",e);
         BindingResult exceptions = e.getBindingResult();
         if (exceptions.hasErrors()) {}
    }

    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)// 设置状态码为500
    @ExceptionHandler(ConstraintViolationException.class)
    public String paramExceptionHandler(ConstraintViolationException e){
        log.error("执行异常",e);
        
    }
}

BindingResult异常

Controller方法的中处理


@PostMapping("addUser")
public Result addUser(@RequestBody @Valid User user,BindingResult result){
    //校验到错误
    if (result.hasErrors()) {
    //获得错误信息列表
    List<String> errMsgs = result.getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(toList());
    String lists = StringUtils.join(lists, ";");
    return new Result(“” "", lists);
    }
    return new Result(“”, "", null);
}

AOP校验

自定义注解


/**
*将此注解加在需要进行参数校验的方法上,
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamValid {
}

切面类


@Aspect
@Component
public class ParamValidAspect {
 
    private static final Logger log = LoggerFactory.getLogger(ParamValidAspect.class);
 
    @Around("@annotation(paramValid)")
    public Object paramValid(ProceedingJoinPoint point, ParamValid paramValid){
        Object[] args = point.getArgs();
        if (paramObj.length > 0){
             for (int b = 0; b < args.length; b++) {
                if (args[b] instanceof BindingResult) {
                    BindingResult bindingResult = (BindingResult) args[b];
                    ResponseMsg errorMap = validRequestParams(bindingResult);
                    if (errorMap != null) {
                        return errorMap;
                    }
                     break;
                }
            }
        }
        try {
            return point.proceed();
        } catch (Throwable throwable) {
            log.error("point.proceed()异常", throwable);
        }
        return null;
    }
 
    /**
     * 校验
     */
    private Result validRequestParams(BindingResult result) {
        if (result.hasErrors()) {
            List<String> errMsgs = result.getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(toList());
            String lists = StringUtils.join(lists, ";");
            return new Result("", "", lists);
        }
        return null;
    }
}

AOP参考:

AspectJ 切面注解中五种通知注解:@Before、@After、@AfterReturning、@AfterThrowing、@Around-CSDN博客

https://www.jianshu.com/p/b924582b8062

@Before 前置通知(Before advice) :
在某连接点(JoinPoint)——核心代码(类或者方法)之前执行的通知,但这个通知不能阻止连接点前的执行。
因为@Before注解的方法入参不能传ProceedingJoinPoint,而只能传入JoinPoint。
从aop走到核心代码就是通过调用ProceedingJionPoint的proceed()方法。而JoinPoint没有这个方法。
@Around 环绕通知(Around advice) :aop的最重要的,最常用的注解。
包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。
可以在方法的调用前后完成自定义的行为,也可以选择不执行。
用这个注解的方法入参传的是ProceedingJionPoint point,可以决定当前线程能否进入核心方法中——通过调用point.proceed();
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 中,可以使用 Hibernate Validator进行请求参数校验。具体步骤如下: 1. 引入 Hibernate Validator 依赖: ```xml <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.1.5.Final</version> </dependency> ``` 2. 在需要校验的请求参数实体类中添加校验注解,例如: ```java public class User { @NotBlank(message = "用户名不能为空") private String username; @NotBlank(message = "密码不能为空") private String password; @Email(message = "邮箱格式不正确") private String email; // 省略 getter 和 setter 方法 } ``` 3. 在请求处理方法中添加 @Validated 注解,并在参数上添加 @Valid 注解,例如: ```java @RestController @RequestMapping("/user") @Validated public class UserController { @PostMapping("/login") public Result login(@RequestBody @Valid User user) { // 处理登录逻辑 } } ``` 4. 当请求参数不符合校验规则时,会抛出 ConstraintViolationException 异常。可以在全局异常处理器中对该异常进行处理,例如: ```java @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(ConstraintViolationException.class) public Result handleConstraintViolationException(ConstraintViolationException e) { List<String> errorMessages = e.getConstraintViolations().stream() .map(ConstraintViolation::getMessage) .collect(Collectors.toList()); return Result.error(String.join(",", errorMessages)); } } ``` 这样就可以自定义请求参数校验了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值