Springboot参数检验

这篇博客详细介绍了SpringBoot中的参数验证,包括常用注解、全局异常处理方法,如 BindException 和 MethodArgumentNotValidException 的区别,以及ConstraintViolationException的使用。还通过示例解释了无组操作和组处理的验证过程,强调了在Controller和Service层如何添加@Valid和@Validated注解来实现参数校验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、常用注解

常用注解说明判断和示例
@NotNull被注解元素不能为nullif(obj != null)
@NotEmpty被注解元素不能为null且长度不能为0字符串:if(s != null && s.length() > 0) 集合:if(obj != null && obj.size() > 0)
@NotBlank只能用于String类型,被注解元素不能为null且必须有非空格外的其它字符if(s != null && s.trim().length() > 0)
@Length(min=low, max=high)只能用于String类型,其长度min <= len <= max。按理说都有length了,肯定不能为null,但是测试不行,故必须与@NotNull等非空注解配合使用if(s != null && s.length() >= min && s.length <= high)
@Size(min=low, max=high)用于集合类型,其大小min <= size <= max。也必须配合非空注解使用if(obj != null && obj.length() >= min && obj.length <= high)
@Max(max)@Min(min)都只能用于Number类型或字符类型。@Max表示被注解元素的值不能超过设置的最大值max;@Min表示被注解的值不能小于min。也必须配合@NotNull注解使用如设置vlanID,最大为4096
@Email只用于String类型。需配合非空注解使用默认的格式:a@b
@Pattern(regexp=正则表达式)用于String类型,regexp设置正则,需配合非空注解使用如校验IP

二、全局异常

0. 依赖管理

需引入依赖包hibernate-validator

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.16.Final</version>
</dependency>

1. BindException / MethodArgumentNotValidException

Controller中进行参数校验时抛出该异常。其中,不涉及组操作时,默认抛出BindException异常;涉及组操作时默认抛出MethodArgumentNotValidException异常,差别不大,新版本中MethodArgumentNotValidException继承于BindException,两个异常可以共用一个处理方法

2. ConstraintViolationException

Service中进行参数校验时抛出此异常,通过其getMessage()方法就可获取所有异常message

3. 代码实现

@RestControllerAdvice/@ControllerAdvice:全局异常处理注解,拦截所有异常统一处理。也是 @Component注解,两者的区别同@RestController/@Controller一样。

@ExceptionHandler:拦截指定异常并进行处理,同一个异常处理不能出现两次

@ResponseStatus:设置response的状态码,默认200 OK

@RestControllerAdvice
public class GlobalException {
    
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResultBack handleConstraintViolationException(ConstraintViolationException ex) {
        BasicLogUtil.info("ConstraintViolationException");

        return new ResultBack(ResultStatus.FAILED, ex.getMessage());
    }

    // BindException 和 MethodArgumentNotValidException 共用一个异常处理方法
    @ExceptionHandler({BindException.class, MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResultBack handleBindException(BindException ex) {
        BasicLogUtil.info("BindException");
        
        StringBuilder errorMsg = new StringBuilder();
        // 获取所有字段验证出错的信息
        List<FieldError> allErrors = ex.getFieldErrors();
        allErrors.forEach(fieldError -> {
            errorMsg.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append("; ");
        });
        return new ResultBack(ResultStatus.FAILED, errorMsg.toString());
    }

    // 同一个异常不能出现在两个@ExceptionHandler注解值中
    // @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResultBack handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        // 新版本中MethodArgumentNotValidException继承于BindException,而不是直接继承Exceprtion
        BasicLogUtil.info("MethodArgumentNotValidException extends BindException");
        
        StringBuilder errorMsg = new StringBuilder();
        // 获取所有错误信息(包括字段验证错误信息FieldError)
        List<ObjectError> allErrors = ex.getAllErrors();
        // 遍历,拼接获取的error的message
        allErrors.forEach(error -> {
            if(error instanceof FieldError) {
                FieldError fieldError = (FieldError) error;
                errorMsg.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append("; ");
            } else {
                errorMsg.append(error.getDefaultMessage()).append("; ");
            }
        });
        return new ResultBack(ResultStatus.FAILED, errorMsg.toString());
    }
}

三、示例一(无组操作)

0. 测试Bean类

@Data
public class TestBean {

    /**
     * 主键
     */
    private Integer id;

    /**
     * 编号
     */
    @NotEmpty(message = "编号不能为null")
    private String numberIdl;

    /**
     * 姓名
     */
    @NotBlank(message = "姓名不能为空")
    @Length(min = 3, max = 10)
    private String name;

    /**
     * 修改人
     */
    @NotNull(message = "操作人不能为null")
    private String modifier;
}

1. controller层验证

直接在参数前加上注解@Valid即可

@RequestMapping("/test")
public Object test(@Valid @RequestBody TestBean testBean) {
    System.out.println(testBean);
    return testBean;
}

2. service层验证

只需在service接口上添加注解即可,service接口的实现类不用添加,具体添加方式为:

  • service接口添加@Validated注解
  • 具体方法要校验的参数添加@Valid注解
@Validated
public interface TestService {
    Object test(@Valid TestBean testBean);
}

接口的实现类,无需任何注解

@Service
public class TestServiceImpl implements TestService {
    @Override
    public Object test(TestBean testBean) {
        return testBean;
    }
}

也可以将这些注解统一放到service的实现类中实现,不过由于override的关系,接口中的test方法参数也必须使用@Valid注解,如下:

public interface TestService {
    // 由于继承重写该方法,故此处必须和重写的方法参数保持一致
    Object test(@Valid TestBean testBean);
}

@Service
@Validated
public class TestServiceImpl implements TestService {
    @Override
    public Object test(@Valid TestBean testBean) {
        return testBean;
    }
}

3. 总结

  1. Controller里的验证直接添加@Valid注解就行
  2. Service里的验证说了那么多,其实就是两个层面的注解:
    service接口/实现类层面上的@Validated注解;
    方法参数层面上校验参数的@Valid注解;

具备这两个层面的注解就行,可以随意组合,如上面所示

四、示例二(组处理)

0. 测试Bean类

@Data
public class TestBeanWithGroup {

    /**
     * 主键
     */
    @NotNull(groups = {GroupUpt.class}, message = "修改时主键不能为null")
    private Integer id;

    /**
     * 编号
     */
    @NotNull(groups = {GroupAdd.class}, message = "新增时编号不能为null")
    private String numberId;

    /**
     * 姓名
     */
    @NotNull(groups = {GroupAdd.class}, message = "新增时姓名不能为null")
    private String name;

    /**
     * 修改人
     */
    @NotNull(groups = {GroupAdd.class, GroupUpt.class}, message = "操作人不能为null")
    private String modifier;

    @NotNull(message = "测试非组操作属性")
    private String noGroup;


    public interface GroupAdd extends Default{};
    public interface GroupUpt extends Default{};
}

上面最后的noGroup属性未指明任何分组,故是默认的分组Default,而分组GroupAdd和GroupUpt都继承了Default,故当校验这两个分组时也会校验noGroup属性,若只想单纯校验分组的属性,可以取消其继承关系。

1. controller层验证

直接在参数前加上注解@Validated,并指明校验的分组。

@RequestMapping("/testGroup")
public Object testWithGroup(@Validated(TestBeanWithGroup.GroupUpt.class) @RequestBody TestBeanWithGroup testBean) {
    System.out.println(testBean);
    return testBean;
}

2. service层验证

同上面不适用分组时一样,两个层面,不过由于使用了分组还需多一个层面:

  • service接口/实现类层面上的@Validated注解
  • 方法参数层面上校验参数的@Valid注解
  • 方法层面上的@Validated注解,并指定分组

两个层次(接口和实现类)共同拥有这三个层面的注解即可。

若只有前两个层面的注解,即相当于第三个层面指定的分组为Default分组,故只会验证Default分组(默认分组)的属性。

// 校验全部在接口中实现
@Validated
public interface TestGroupService {
    // 指明该校验的分组
    @Validated(TestBeanWithGroup.GroupAdd.class)
    Object test(@Valid TestBeanWithGroup testBeanWithGroup);
}



// 实现类中指明校验的分组
@Validated
public interface TestGroupService {
    Object test(@Valid TestBeanWithGroup testBeanWithGroup);
}

@Service
public class TestGroupServiceImpl implements TestGroupService {
    @Override
    @Validated(TestBeanWithGroup.GroupAdd.class)
    public Object test(TestBeanWithGroup testBeanWithGroup) {
        return testBeanWithGroup;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值