1 引入依赖Spring Boot 3.x 也使用这个 starter但包名迁移到了 jakarta.validation。dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-validation/artifactId/dependency2 在 DTO 上加注解publicclassUserDTO{NotBlank(message用户名不能为空)privateStringusername;Email(message邮箱格式不正确)privateStringemail;NotNull(message年龄不能为空)Min(value0,message年龄不能小于0)privateIntegerage;}3 Valid触发默认校验PostMapping(/users)publicResultaddUser(ValidRequestBodyUserDTOuser){returnResult.success(添加成功);}4 Validated触发分组校验4.1 定义分组接口publicinterfaceAddGroup{}publicinterfaceUpdateGroup{}4.2 在 DTO 上用 groups 指定分组注意没写 groups 的注解属于 默认分组 Default.class。如果写了 groups它只属于指定分组默认分组不会触发。publicclassUserDTO{// 新增和修改都需要 idNotNull(messageid不能为空,groups{UpdateGroup.class})privateLongid;NotBlank(message用户名不能为空,groups{AddGroup.class,UpdateGroup.class})privateStringusername;Email(message邮箱格式不正确,groups{AddGroup.class})privateStringemail;NotNull(message年龄不能为空,groups{AddGroup.class,UpdateGroup.class})Min(value0,message年龄不能小于0,groups{AddGroup.class,UpdateGroup.class})privateIntegerage;}4.3 方法参数上用 groups 指定分组PostMapping(/add)publicRadd(Validated(AddGroup.class)RequestBodyOrderDTOorder){...}5 Valid和Validated区别参考文章别再混淆了一文带你搞懂Valid和Validated的区别维度ValidValidated来源JSR 303/380 标准javax.validation/jakarta.validationSpring 扩展org.springframework.validation.annotation分组校验❌ 不支持✅ 支持Validated(AddGroup.class)嵌套校验✅ 支持可标在字段上触发子对象校验❌ 不支持不能标在字段上可用位置方法参数、成员变量递归校验、构造函数参数方法参数、方法、类 开启该类校验校验失败异常MethodArgumentNotValidExceptionMethodArgumentNotValidExceptionController 参数ConstraintViolationExceptionService 层 AOP5.1 类上的 Validated开启方法级校验下面情况 类上不用添加Validated开启方法校验默认触发校验。场景情况RequestBody对象参数Valid RequestBody会触发 DTO 内部校验ModelAttribute/ 表单绑定对象表单提交、查询参数绑定到对象时Valid也能触发校验RequestParam、PathVariable、Service 方法参数这些不是RequestBodyValid放在参数上通常不会自动生效需要类上加Validated走方法级校验下面情况 类上必须加Validated开启方法校验否则NotBlank校验不生效RestControllerRequestMapping(/brand)Validated// 关键类上必须加 ValidatedpublicclassBrandController{/** * 按品牌名称查询 */GetMapping(/search)publicRsearch(RequestParam(name)NotBlank(message品牌名称不能为空)Stringname){returnR.ok();}}5.2 方法参数上的Valid/Validated分组与嵌套Validated可以实现分组校验Valid无此功能。 Valid可以实现嵌套校验Validated不能触发。public R save(Valid RequestBody BrandEntity brand)public R save(Validated(AddGroup.class) RequestBody BrandEntity brand)嵌套校验举例publicclassBrandEntity{privateStringname;// 嵌套对象privateAddressEntityaddress;}如果你用 ValidSpring 不仅会校验 BrandEntity 的 name还会自动进入 AddressEntity 里面去校验它的字段。如果你用Validated不会进入 AddressEntity 里面。如果想要同时实现分组校验和嵌套校验Validated修饰方法参数并在AddressEntity嵌套对象上添加Valid。5.3 成员变量上的Valid嵌套Valid private Address address;这是它和 Validated 的关键区别之一开启嵌套校验publicclassUserDTO{NotBlank(message姓名不能为空)privateStringname;NotNull(message地址不能为空)Valid// 加上 Valid会递归校验嵌套对象privateAddressDTOaddress;}所以Validated修饰的方法参数内部可以在成员private AddressDTO address上添加Valid实现分组校验和嵌套校验。6 异常处理6.1全局异常处理参考文章SpringBoot 使用Spring Validation实现接口参数校验全局异常处理类只需要在类上标注RestControllerAdvice并在处理相应异常的方法上使用ExceptionHandler注解写明处理哪个异常即可。RestControllerAdvice本质上是Component可以注入全局异常处理bean。配置basePackages参数处理指定包下抛出的异常。如果不指定则会处理所有的Controller标注的类抛出的异常。ExceptionHandler 标注异常处理方法其value标注要处理的异常类型一般配置一个value Exception.class处理其他未配置异常类型RestControllerAdvice(basePackagescom.atguigu.gulimall.product.controller)publicclassGulimallExceptionControllerAdvice{ExceptionHandler(valueMethodArgumentNotValidException.class)publicRhandleValidException(MethodArgumentNotValidExceptione){BindingResultbindingResulte.getBindingResult();HashMapString,StringerroMapnewHashMap();bindingResult.getFieldErrors().forEach((fieldError-{erroMap.put(fieldError.getField(),fieldError.getDefaultMessage());}));returnR.error(BizCodeEnume.VALID_EXCEPTION.getCode(),BizCodeEnume.VALID_EXCEPTION.getMsg()).put(data,erroMap);}ExceptionHandler(valueException.class)publicRhandleException(Exceptionexception){returnR.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());}}6.2 BingResultBindingResult 是 Spring MVC 里用来接收参数校验结果的对象。校验失败时它会把错误信息存起来而不会直接抛异常。它必须紧跟在被 Valid 或 Validated 标注的参数后面PostMapping(/user)publicStringcreateUser(ValidRequestBodyUseruser,BindingResultresult){if(result.hasErrors()){Stringmsgresult.getFieldErrors().stream().map(e-e.getField(): e.getDefaultMessage()).collect(Collectors.joining(; ));returnResult.fail(400,msg);}...}方法作用hasErrors()是否有校验错误hasFieldErrors()是否有字段级别的错误getFieldErrors()获取所有字段错误列表getFieldError(name)获取指定字段的第一个错误getAllErrors()获取所有错误含全局错误getErrorCount()错误总数7 自定义校验注解7.1 自定义注解PhoneDocumentedConstraint(validatedBy{PhoneValidator.class})Target({ElementType.FIELD,ElementType.PARAMETER})Retention(RetentionPolicy.RUNTIME)publicinterfacePhone{Stringmessage()default手机号格式校验不正确;Class?[]groups()default{};Class?extendsPayload[]payload()default{};StringphoneReg()default^1[3-9]\\d{9}$;}Constraint(validatedBy PhoneValidator.class):指定7.2 注解校验处理方法PhoneValidator这里三个属性是 Bean Validation 规范要求保留的属性作用message()校验失败时的提示信息groups()分组校验使用payload()附加元数据一般默认即可7.2 注解校验处理方法publicclassPhoneValidatorimplementsConstraintValidatorPhone,String{privateStringregPhone;Overridepublicvoidinitialize(PhoneconstraintAnnotation){//获取注解中phoneReg的值StringphoneRegconstraintAnnotation.phoneReg();this.regPhonephoneReg;}OverridepublicbooleanisValid(Stringvalue,ConstraintValidatorContextcontext){//value匹配正则表达式返回是否符合//为空时交由其他校验如NotBlank处理此处直接视为通过if(valuenull||value.isEmpty()){returntrue;}returnvalue.matches(this.regPhone);}}ConstraintValidatorA, T的两个泛型含义是A自定义注解类型。T被校验字段的数据类型。isValid(String value, ConstraintValidatorContext context)两个参数含义value当前正在被校验的那个值。context校验上下文对象用来控制“校验失败后怎么报错”。修改错误提示的message消息。isValid方法返回布尔类型代表校验通过与否。7.3 使用· DTO层DatapublicclassPhoneDTO{NotNull(message姓名不能为空)privateStringname;Phone(phoneReg^15[3-9]\\d{8}$,message请填写15开头的手机号码)privateStringphone;}· Controller层RestControllerRequestMapping(product/category)publicclassCategoryController{RequestMapping(/test/phone)publicRtestPhone(ValidRequestBodyPhoneDTOphone,BindingResultbindingResult){StringerrorMessagenull;if(bindingResult.hasErrors()){errorMessagebindingResult.getFieldErrors().get(0).getDefaultMessage();returnR.error(400,参数错误).put(errors,errorMessage);}returnR.ok().put(page,errorMessage);}}7.4 Http测试POST http://localhost:10001/product/category/test/phone Content-Type: application/json { name: test, phone: 1381234567 }8 常见的Validation注解8.1 空值校验注解适用类型校验规则用例NotNull任意类型不能为null但可以是空字符串、空集合NotNull(messageid不能为空) private Long id;NotBlankString不能为null且去除首尾空格后长度必须大于 0NotBlank(message用户名不能为空) private String username;NotEmptyString、集合、数组、Map不能为null且长度/元素个数大于 0NotEmpty(message标签不能为空) private ListString tags;简单区分字符串必填优先用 NotBlank集合/数组必填用 NotEmpty非字符串对象、数字、日期用 NotNull。8.2 字符串格式与长度注解适用类型校验规则用例Min数值类型必须 ≥ 指定值Min(18) private Integer age;Max数值类型必须 ≤ 指定值Max(120) private Integer age;DecimalMin数值类型、字符串数字必须 ≥ 指定值适合金额、小数DecimalMin(0.00) private BigDecimal price;DecimalMax数值类型、字符串数字必须 ≤ 指定值DecimalMax(99999.99) private BigDecimal price;Positive数值类型必须大于 0Positive private Integer count;PositiveOrZero数值类型必须 ≥ 0PositiveOrZero private BigDecimal amount;Negative数值类型必须小于 0Negative private Integer loss;NegativeOrZero数值类型必须 ≤ 0NegativeOrZero private BigDecimal change;Digits数值类型限制整数位数和小数位数Digits(integer10, fraction2) private BigDecimal amount;8.3 日期时间校验注解适用类型校验规则用例Past日期时间必须是过去时间Past private LocalDate birthday;PastOrPresent日期时间必须是过去或当前时间PastOrPresent private LocalDate startDate;Future日期时间必须是未来时间Future private LocalDate expireDate;FutureOrPresent日期时间必须是未来或当前时间FutureOrPresent private LocalDateTime endTime;8.4 布尔校验注解适用类型校验规则用例AssertTrueBoolean必须为trueAssertTrue private Boolean agreed;AssertFalseBoolean必须为falseAssertFalse private Boolean deleted;