8030 字
17 分钟

Spring Boot参数校验详解

文章摘要
DeepSeek R1
本文详细介绍了在Spring Boot 2.3.3中使用Hibernate Validator进行参数校验的方法。内容涵盖基础参数校验、嵌套参数验证、分组参数验证以及全局异常处理。通过具体的实体类、控制器代码示例和测试结果,展示了@Validated和@Valid注解的用法与区别,帮助开发者高效实现API参数验证,确保数据传输的准确性与安全性。

Spring Boot参数校验实践

技术栈

  • Spring Boot 2.3.3.RELEASE
  • Hibernate Validator

概述

@Validated 是Spring框架提供的注解,用于对方法参数进行数据校验。配合Hibernate Validator的约束注解,可以轻松实现对数据的验证。

@Validated 可以作用于类、方法和参数上。

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {
    Class[] value() default {};
}

HTTP状态码建议 校验失败时,推荐返回 400 Bad Request

常用校验注解 校验注解说明

项目依赖


    org.springframework.boot
    spring-boot-starter-validation

全局异常处理

若不进行异常处理,@Validated 的默认异常信息较为冗长:

2020-09-05 21:48:38.106  WARN 9796 --- [nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String com.example.validateddemo.controller.DemoController.validatedDemo1(com.example.validateddemo.entity.dto.UseDto): [Field error in object 'useDto' on field 'username': rejected value [null]; codes [NotBlank.useDto.username,NotBlank.username,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [useDto.username,username]; arguments []; default message [username]]; default message [用户名不能为空!]] ]

因此,我们需要创建全局异常处理器,统一处理参数校验异常:

package com.example.validateddemo.handler;

import com.example.validateddemo.base.Result;
import com.example.validateddemo.enums.ResultEnum;
import com.example.validateddemo.utils.ResultUtil;
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.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import java.util.List;

/**
 * 参数校验异常处理器
 * @author He Changjie on 2020/9/5
 */
@Slf4j
@ControllerAdvice
public class ValidatedExceptionHandler {

    /**
     * 处理@Validated参数校验失败异常
     * @param exception 异常类
     * @return 响应结果
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result exceptionHandler(MethodArgumentNotValidException exception){
        BindingResult result = exception.getBindingResult();
        StringBuilder stringBuilder = new StringBuilder();
        if (result.hasErrors()) {
            List errors = result.getAllErrors();
            if (errors != null) {
                errors.forEach(p -> {
                    FieldError fieldError = (FieldError) p;
                    log.warn("Bad Request Parameters: dto entity [{}],field [{}],message [{}]",fieldError.getObjectName(), fieldError.getField(), fieldError.getDefaultMessage());
                    stringBuilder.append(fieldError.getDefaultMessage());
                });
            }
        }
        return ResultUtil.validatedException(stringBuilder.toString());
    }
}

基础参数校验

实体类

package com.example.validateddemo.entity.dto;

import lombok.Data;
import javax.validation.constraints.*;

/**
 * 用户数据传输对象
 * @author He Changjie on 2020/9/5
 */
@Data
public class User1Dto {
    @NotBlank(message = "用户名不能为空!")
    private String username;
    
    @NotBlank(message = "性别不能为空!")
    private String gender;
    
    @Min(value = 1, message = "年龄有误!")
    @Max(value = 120, message = "年龄有误!")
    private int age;
    
    @NotBlank(message = "地址不能为空!")
    private String address;
    
    @Email(message = "邮箱有误!")
    private String email;
    
    @Pattern(regexp = "^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}$", message = "手机号码有误!")
    private String mobile;
}

控制器

package com.example.validateddemo.controller;

import com.example.validateddemo.entity.dto.User1Dto;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1")
public class Demo1Controller {

    @PostMapping("/insert")
    public String validatedDemo1(@Validated @RequestBody User1Dto user1Dto){
        System.out.println(user1Dto);
        return "success";
    }
}

测试结果

  1. 参数校验通过 校验通过

  2. 参数校验不通过 校验失败

嵌套参数验证

验证实体中包含的其他对象或集合。

实体类

package com.example.validateddemo.entity.dto;

import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;

@Data
public class Team1Dto {
    @NotBlank(message = "队伍名称不能为空!")
    private String name;
    
    @NotNull(message = "队伍人员不能为空!")
    @Valid
    private List userList;
    
    @NotNull(message = "队伍负责人不能为空!")
    @Valid
    private User1Dto user;
}

控制器方法

@PostMapping("/insert2")
public Result validatedDemo2(@Validated @RequestBody Team1Dto team1Dto){
    return ResultUtil.success(team1Dto);
}

测试结果

  1. 参数验证通过 嵌套验证通过

  2. 参数验证不通过 嵌套验证失败1 嵌套验证失败2

分组参数验证

通过分组机制,在不同场景下应用不同的校验规则。

分组接口

package com.example.validateddemo.interfaces;

public interface Group1 {}
package com.example.validateddemo.interfaces;

public interface Group2 {}

实体类

package com.example.validateddemo.entity.dto;

import com.example.validateddemo.interfaces.Group1;
import com.example.validateddemo.interfaces.Group2;
import lombok.Data;
import javax.validation.constraints.*;

@Data
public class User2Dto {
    @NotBlank(message = "用户名不能为空!", groups = {Group1.class})
    private String username;
    
    @NotBlank(message = "性别不能为空!")
    private String gender;
    
    @Min(value = 1, message = "年龄有误!", groups = {Group1.class})
    @Max(value = 120, message = "年龄有误!", groups = {Group2.class})
    private int age;
    
    @NotBlank(message = "地址不能为空!")
    private String address;
    
    @Email(message = "邮箱有误!", groups = {Group2.class})
    private String email;
    
    @Pattern(regexp = "^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}$",
             message = "手机号码有误!", groups = {Group2.class})
    private String mobile;
}

控制器方法

@PostMapping("/insert4")
public Result validatedDemo4(@Validated(Group1.class) @RequestBody User2Dto user2Dto){
    return ResultUtil.success(user2Dto);
}

@PostMapping("/insert5")
public Result validatedDemo5(@Validated(Group2.class) @RequestBody User2Dto user2Dto){
    return ResultUtil.success(user2Dto);
}

@Valid与@Validated区别

@Valid源码

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Valid {}

@Validated源码

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {
    Class[] value() default {};
}

主要区别

  1. 分组功能@Validated支持分组,@Valid不支持
  2. 使用范围
    • @Valid可用于方法、字段、构造函数、参数和类型使用
    • @Validated可用于类型、方法和参数,但不能用于字段
  3. 嵌套验证@Valid可用于字段,因此支持嵌套验证;@Validated不能直接用于字段

总结:在Spring项目中,通常使用@Validated进行方法参数校验,使用@Valid进行嵌套对象校验。


源码地址:[码云项目地址]

原创不易,转载请注明来源 文章来源:https://blog.csdn.net/qq_32352777/article/details/108424932

Firefly
Firefly
Hello, I'm Firefly.
公告
欢迎体验 Firefly 主题复刻版,壁纸与布局已全面同步。
查看文档