注解参数校验——@Vaild和@Validated在Service使用

4,838 阅读5分钟

一、常用注解

注解分类含义描述
@NotEmpty空和非空集合不能为空用在集合上面,一般用来校验List类型(不能注释枚举),而且长度必须大于0
@NotBlankString不能为空用在String上面,一般用来校验String类型不能为空,而且调用trim()后,长度必须大于0。
@NotNull所有类型不能为null用在所有类型上面,一般用来校验Integer类型不能为空,但可以为empty。
@Null元素必须为null元素必须为null
@AssertTrueBoolean值元素必须为true元素必须为true
@AssertFalse元素必须是false元素必须是false
@Min(value)数值最小值元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)最大值元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)最小值(包含)元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)最大值(包含)元素必须是一个数字,其值必须小于等于指定的最大值
@Negative负整数负整数
@NegativeOrZero负整数或零负整数或零
@Positive正整数正整数
@PositiveOrZero正整数或零正整数或零
@Digits(integer,fraction)值范围元素必须是一个数字,其值必须在可接受的范围内
@Size(max,min)范围长度范围元素的大小必须在指定的范围内(可以处理数组、集合)
@Length字符串长度元素的大小必须在指定的范围内(只能处理字符串)
@Range值范围元素必须在合理的范围内
@Past日期过去日期元素必须是一个过去的日期
@PastOrPresent过去日期或当前日期
@Future未来日期元素必须是一个将来的日期
@FutureOrPresent未来日期或当前日期
@Pattern(value)其他正则匹配元素必须符合指定的正则表达式
@Email邮箱元素必须是电子邮箱地址

二、@Valid与@Validated的使用与区别

Spring Validation验证框架对参数的验证机制提供了@Validated(Spring's JSR-303规范,是标准JSR-303的一个变种),javax提供了@Valid(标准JSR-303规范),配合BindingResult可以直接提供参数验证结果。

@Valid、@Validated 注解可以实现数据的验证,你可以定义实体,在实体的属性上添加校验规则,校验规则如上@NotEmpty,@NotBlank,@NotNull等等的注解就是,不加上就不会生效!

@Valid 包位置:javax.validation

@Validated 包位置 org.springframework.validation.annotation

是@Valid 的一次封装,是Spring提供的校验机制使用。

2.1@Valid与@Validated区别:

相同点:  在检验Controller的入参是否符合规范时,使用@Validated或者@Valid都可以实现,并且需要传入BindingResult对象,用于获取校验失败情况下的反馈信息,如下代码:

@PostMapping("register")
//@Valid这两个用哪个效果都一样
public JSONResult register(@Validated @RequestBody RegisterVo registerVo, BindingResult bindingResult){

    //会把校验失败情况下的反馈信息
    if (bindingResult.hasErrors()) {
        System.out.println(bindingResult.getFieldError().getDefaultMessage());
        return JSONResult.error().message(bindingResult.getFieldError().getDefaultMessage());
    }
    memberService.register(registerVo);
    return JSONResult.ok();
}

不同点:2个方面如下

1、分组上:

@Valid 不支持分组, @Validated则支持分组验证。

2、使用位置:

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

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

2.2嵌套验证

class Person {
    @NotNull
    private Integer id;

    @NotNull
    /**
     * @Validated不允许使用在成员属性上,所以只能用@Valid
     * 如果不加  @Valid 就不会对 Son类进行校验,只会判断 @NotNull,List<Son>是否为空,并不会Son类里面的成员属性
     */
    @Valid //嵌套验证
    private List<Son> son;
}

class Son {
    @NotNull
    private Integer sid;

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

}

class MemberController {

    @ApiOperation(value = "会员注册")
    @PostMapping("register")
                              //使用@Validated或者@Valid都可以,这样就完成了嵌套验证
    public JSONResult register(@Validated @RequestBody Person person, BindingResult bindingResult){

        if (bindingResult.hasErrors()) {
            System.out.println(bindingResult.getFieldError().getDefaultMessage());
            return JSONResult.error().message(bindingResult.getFieldError().getDefaultMessage());
        }
        return JSONResult.ok();
    }

}

三、在Service层中使用@Vaild

3.1对象校验

3.1.1在接口上入参上增加@vaild注解

3.1.2在接口实现类上增加@Vaildated注解,在入参增加@vaild注解

3.1.3在入参实体类中增加相关注解

3.2方法及值校验

在接口上增加@Validated注解,在入参上增加对应校验注解

@Validated(Default.class)
public interface HelloService {
    Object hello(@NotNull @Min(10) Integer id, @NotNull String name);
}
 
public class HelloServiceImpl implements HelloService {
    @Override
    public Object hello(Integer id, String name) {
        return null;
    }
}

四、全局异常处理

如果我们直接使用e.getMessage()获得报错信息的话,报错信息太长了,并不能直接返回给前端,前端也看不懂,所以需要进行处理。

也可使用AOP切面实现

异常类型:

BindException表单提交有效,对于以json格式提交将会失效
MethodArgumentNotValidException前段以json格式有效
ConstraintViolationExceptionjsr 规范中的验证异常,嵌套检验问题

4.1参考一

@ControllerAdvice
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResponseData exceptionHandler(MethodArgumentNotValidException ex) {
        System.out.println("全局异常处理捕捉");
        BindingResult result = ex.getBindingResult();
        String message = "";
        if (result.hasErrors()) {
            List<ObjectError> errors = result.getAllErrors();
            if (errors != null) {
                errors.forEach(p -> {
                    FieldError fieldError = (FieldError) p;
                    log.error("Data check failure : object{" + fieldError.getObjectName() + "},field{" + fieldError.getField() +
                            "},errorMessage{" + fieldError.getDefaultMessage() + "}");

                });
                if (errors.size() > 0) {
                    FieldError fieldError = (FieldError) errors.get(0);
                    message = fieldError.getDefaultMessage();
                }
            }
        }
        message = "".equals(message) ? "参数不正确!":message;
        return ResponseData.defaultFail(message);
    }
}

4.2参考二

import cn.hutool.core.util.StrUtil;
import com.course.commons.dto.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
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.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Title:统一异常,返回json
 * Description:
 * @author WZQ
 * @version 1.0.0
 * @date 2021/4/22
 */
@RestControllerAdvice(annotations = {RestController.class, Controller.class})
@Slf4j
public class BaseExceptionHandler {

    /**
     * 空参异常处理
     * @param ex
     * @param request
     * @return
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseResult<Void> bindException(BindException ex, HttpServletRequest request) {
        log.warn("BindException:", ex);
        try {
            // 拿到@NotNull,@NotBlank和 @NotEmpty等注解上的message值
            String msg = Objects.requireNonNull(ex.getBindingResult().getFieldError()).getDefaultMessage();
            if (StrUtil.isNotEmpty(msg)) {
                // 自定义状态返回
                return new ResponseResult<>(ResponseResult.CodeStatus.FAIL, msg);
            }
        } catch (Exception ignored) {
        }
        // 参数类型不匹配检验
        StringBuilder msg = new StringBuilder();
        List<FieldError> fieldErrors = ex.getFieldErrors();
        fieldErrors.forEach((oe) ->
                msg.append("参数:[").append(oe.getObjectName())
                        .append(".").append(oe.getField())
                        .append("]的传入值:[").append(oe.getRejectedValue()).append("]与预期的字段类型不匹配.")
        );
        return new ResponseResult<>(ResponseResult.CodeStatus.FAIL, msg.toString());
    }

    /**
     * jsr 规范中的验证异常,嵌套检验问题
     * @param ex
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseResult<Void> constraintViolationException(ConstraintViolationException ex, HttpServletRequest request) {
        log.warn("ConstraintViolationException:", ex);
        Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
        String message = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));

//        ConstraintViolation<?> violation = violations.iterator().next();
//        String path = ((PathImpl) violation.getPropertyPath()).getLeafNode().getName();
//        String message2 = String.format("%s:%s", path, violation.getMessage());

        return new ResponseResult<>(ResponseResult.CodeStatus.FAIL, message);
    }

    /**
     * spring 封装的参数验证异常, 在controller中没有写result参数时,会进入
     * @param ex
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseResult<Void> methodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
        log.warn("MethodArgumentNotValidException:", ex);
        return new ResponseResult<>(ResponseResult.CodeStatus.FAIL, Objects.requireNonNull(ex.getBindingResult().getFieldError()).getDefaultMessage());
    }
}

五、Maven依赖

    <!--第一种方式导入校验依赖-->
    <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>
    </dependency>

六、总结

1、常用校验注解@NotEmpty,@NotBlank,@NotNull,需要搭配@Valid或@Validated使用

2、@Valid或@Validated 中有 BindingResult,可以获取校验失败情况下的反馈信息,如果不添加,默认情况下会抛出对应的异常。

3、@Validated无法单独提供嵌套验证功能。不能用在成员属性上,能配合嵌套验证注解@Valid进行嵌套验证。

@Valid无法单独提供嵌套验证功能。能够用在成员属性上,能配合嵌套验证注解@Valid进行嵌套验证。 

正则校验:blog.csdn.net/lk14478/art…