zoukankan      html  css  js  c++  java
  • @validate或@valid注解进行数据校验的解决方案

    @validate或@valid注解进行数据校验的解决方案

    我们在对外提供接口的时候,为了提高安全性,我们需要在后端做数据的校验。实际上,Java 早在 2009 年就提出了 Bean Validation 规范,该规范定义的是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。并且已经历经 JSR303、JSR349、JSR380 三次标准的置顶,发展到了 2.0 。下面即将要介绍的是该数据验证的规范,以及相应的技术框架日常使用。

    JSR规范提案

    JSR:Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务,JSR已成为Java界的一个重要标准。
    本文介绍的Bean Validation 就是出自JSR303,JSR349,以及JSR380 规范提案。该规范从JSR 303 发展到 JSR 380,目前最新规范是Bean Validation 2.0。
    相信有小伙伴想去看下到底是个啥。规范提案地址:https://jcp.org/en/jsr/summary?id=bean+validation

    需要注意的是,规范提案只是提供了规范,并没有提供具体的实现。具体实现框架有默认的javax.validation.api,以及hibernate-validator。目前绝大多使用hibernate-validator。

    依赖引入

    要使用注解进行校验,需要引入如下两个依赖:

    <dependency>
    	<groupId>javax.validation</groupId>
    	<artifactId>validation-api</artifactId>
    	<version>2.0.1.Final</version>
    </dependency>
    
    <dependency>
      <groupId>org.hibernate.validator</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.0.20.Final</version>
    </dependency>
    
    

    据我发现: 2.2.9.RELEASE 版的 spring-boot-starter-web 中引入了 spring-boot-starter-validation ,而 spring-boot-starter-validation 中又引入了 hibernate-validator ,所以如果引入了 spring-boot-starter-web 或者 spring-boot-starter-validation 都已经默认引入了 hibernate-validator 可以不用再引。

    其它版本还未研究过,在使用的时候可以自己研究一下。

    JSR303定义的校验类型

    空检查
    @Null			验证对象是否为null
    @NotNull		验证对象是否不为null, 无法查检长度为0的字符串
    @NotBlank		检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
    @NotEmpty		检查约束元素是否为NULL或者是EMPTY. 
    
    Booelan检查
    @AssertTrue		验证 Boolean 对象是否为 true  
    @AssertFalse	验证 Boolean 对象是否为 false  
    
    长度检查
    @Size(min=, max=)		验证对象(Array,Collection,Map,String)长度是否在给定的范围之内  
    @Length(min=, max=)		验证注解的元素值长度在min和max区间内
    
    日期检查
    @Past		验证 Date 和 Calendar 对象是否在当前时间之前  
    @Future		验证 Date 和 Calendar 对象是否在当前时间之后  
    @Pattern	验证 String 对象是否符合正则表达式的规则
    
    数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null
    @Min			验证 Number 和 String 对象是否大等于指定的值  
    @Max			验证 Number 和 String 对象是否小等于指定的值  
    @DecimalMax		被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
    @DecimalMin		被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
    @Digits			验证 Number 和 String 的构成是否合法  
    @Digits(integer=,fraction=)		验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
    
    @Range(min=, max=)	验证注解的元素值在最小值和最大值之间
    @Range(min=10000,max=50000,message="range.bean.wage")
    private BigDecimal wage;
    
    @Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
    @CreditCardNumber信用卡验证
    @Email  验证是否是邮件地址,如果为null,不进行验证,算通过验证。
    @ScriptAssert(lang= ,script=, alias=)
    @URL(protocol=,host=, port=,regexp=, flags=)
    
    

    @Valid和@Validated的区别

    @Valid注解是javax提供的,遵循标准 JSR-303 规范,所属包为: javax.validation.Valid

    配合BindingResult可以直接提供参数验证结果。

    @Validated是@Valid的一次封装,是Spring提供的校验机制使用,遵循 Spring’s JSR-303 规范(是标准 JSR-303 的一个变种),所属包为: org.springframework.validation.annotation.Validated

    @Validation对@Valid进行了二次封装,在基本使用上并没有区别,但在分组、注解位置、嵌套验证等功能上有所不同,这里主要就这几种情况进行说明。

    注解位置

    @Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上

    @Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上

    两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能。

    分组

    先定义分组接口(接口什么都不需要,空的就可以):

    public interface Insert {
    }
    
    public interface Update {
    }
    

    在需要校验的bean上加上分组注解:

    @NotBlank(groups = {Update.class}, message = "ID不能为空")
    private String id;
    
    @NotBlank(groups = {Insert.class, Update.class}, message = "名称不能为空")
    @Size(groups = {Insert.class, Update.class}, max = 32, message = "名称最大长度为32")
    private String name;
    

    根据需要,在Controller处理请求中加入 @Validated 并引入需要校验的分组(未引入分组则都校验)

    @PostMapping("/insert")
    public int insert(@RequestBody @Validated({Insert.class}) HospitalRequest request) {
        return hospitalService.insert(request);
    }
    
    @PostMapping("/update")
    public int update(@RequestBody @Validated({Update.class}) HospitalRequest request) {
        return hospitalService.update(request);
    }
    

    在进行insert的时候不会对id进行校验

    嵌套验证

    嵌套验证就是类嵌套类的验证,比如我要在集合上加一个@NotNull的注解,要求该集合中的每一个对象都被验证,如果只用@Validated与@Valid是不会验证的。我们要用@Validated配合@Valid来进行验证。

    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class HospitalRequest {
        /**
         * ID
         */
        @NotBlank(groups = {Update.class}, message = "ID不能为空")
        private String id;
        /**
         * 名称
         */
        @NotBlank(groups = {Insert.class, Update.class}, message = "名称不能为空")
        @Size(groups = {Insert.class, Update.class}, max = 32, message = "名称最大长度为32")
        private String name;
        /**
         * 科室
         */
        @NotBlank(message = "departmentList不能为空")
        @Size(min = 1, message = "至少要有一个属性")
        private List<Department> departmentList;
    }
    

    例如我想让departmentList中的每一个元素都按照我规定的JSR-303校验进行验证。
    那么我在controller中不管用@Validated还是@Valid都是不能验证的。

    只需要在前面加上@Validated注解

    @PostMapping("/add")
    public void add(@RequestBody @Validated HospitalRequest request) {
        add();
    }
    

    然后把@Valid放到需要验证的集合上就可以了:

    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class HospitalRequest {
        /**
         * ID
         */
        @NotBlank(groups = {Update.class}, message = "ID不能为空")
        private String id;
        /**
         * 名称
         */
        @NotBlank(groups = {Insert.class, Update.class}, message = "名称不能为空")
        @Size(groups = {Insert.class, Update.class}, max = 32, message = "名称最大长度为32")
        private String name;
        /**
         * 科室
         */
        @Valid //嵌套验证必须用@Valid
        @NotBlank(groups = {Insert.class, Update.class}, message = "departmentList不能为空")
        @Size(groups = {Insert.class, Update.class}, min = 1, message = "至少要有一个属性")
        private List<Department> departmentList;
    }
    

    使用BindingResult接收校验结果信息

    使用注解进行校验的时候,我们可以通过BindingResult来收集校验结果信息,具体操作如下:

    Controller中,在@Valid或@Validated修饰的参数后跟上BindingResult参数(@Valid或@Validated 和 BindingResult 是一 一对应的,如果有多个@Valid或@Validated,那么每个@Valid或@Validated后面都需要添加BindingResult用于接收bean中的校验信息)

    @PostMapping("/insert")
    public int insert(@RequestBody @Validated HospitalRequest request, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            List<String> collect = bindingResult.getFieldErrors().stream().map(
                    DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.toList());
            StringBuilder errorMsg = new StringBuilder();
            for (String s : collect) {
                errorMsg.append(s);
                errorMsg.append(",");
            }
            errorMsg = new StringBuilder(errorMsg.substring(0, errorMsg.length() - 1));
            log.error("校验未通过:{}", errorMsg.toString());
            Assert.state(Boolean.FALSE, errorMsg.toString());
        }
        return hospitalService.insert(request);
    }
    

    这样就可以接收到校验的结果信息,可以根据校验的结果信息进行一系列操作,如打印错误信息、抛出指定异常等。

    统一异常处理

    在日常开发中,我们可能需要让校验返回指定的信息或对象,这时我们就可以进行统一异常处理:

    package com.app.config;
    
    import com.framework.common.domain.ErrorResponse;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.http.HttpStatus;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.FieldError;
    import org.springframework.validation.ObjectError;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseStatus;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    import java.util.List;
    
    /**
     * 参数校验异常处理
     */
    @Slf4j
    @RestControllerAdvice
    public class BadRequestExceptionHandler {
    
        /**
         * 校验错误拦截处理
         *
         * @param exception 错误信息集合
         * @return ErrorResponse 错误响应,当HTTP响应状态码不为200时,使用该响应返回
         */
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ExceptionHandler(MethodArgumentNotValidException.class)
        private ErrorResponse validateRequestException(MethodArgumentNotValidException exception) {
            BindingResult bindingResult = exception.getBindingResult();
            StringBuilder errorMsg = new StringBuilder();
            if (bindingResult.hasErrors()) {
                List<ObjectError> errors = bindingResult.getAllErrors();
                for (ObjectError objectError : errors) {
                    FieldError fieldError = (FieldError) objectError;
                    if (log.isDebugEnabled()) {
                        log.error("Data check failure : object: {},field: {},errorMessage: {}",
                                fieldError.getObjectName(), fieldError.getField(), fieldError.getDefaultMessage());
                    }
                    errorMsg.append(objectError.getDefaultMessage());
                    errorMsg.append(",");
                }
                errorMsg = new StringBuilder(errorMsg.substring(0, errorMsg.length() - 1));
            }
            return new ErrorResponse("ILLEGAL_ARGUMENT_ERROR", errorMsg.toString());
        }
    }
    

    返回的自定义响应体如下:

    package com.framework.common.domain;
    
    import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    /**
     * 错误响应,当HTTP响应状态码不为200时,使用该响应返回
     */
    @JsonIgnoreProperties(ignoreUnknown = true)
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    public class ErrorResponse {
    
    	/**
    	 * 错误码
    	 */
    	private String code;
    
    	/**
    	 * 错误信息
    	 */
    	private String message;
    
    }
    
  • 相关阅读:
    PHP Context学习系列《十》
    学习php记录《九》
    学习php记录《八》
    php学习记录《七》
    换到新工作后
    学习php记录《六》
    学习php记录《五》
    学习php记录《四》
    学习php记录《三》
    html基础
  • 原文地址:https://www.cnblogs.com/curtinliu/p/14098692.html
Copyright © 2011-2022 走看看