spring常用注解(六)@Valid和@Validated校验

2025-07-28 12:26:31

一、@Valid注解

1、介绍

@Valid注解可以作用于:方法、属性(包括枚举中的常量)、构造函数、方法的形参上。

import javax.validation.Valid;

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface Valid {

}

2、参数校验使用注解

(1)空校验

注解应用@Null用于基本类型上,限制只能为null@NotNull用在基本类型上;不能为null,但可以为empty,没有Size的约束@NotEmpty用在集合类上面;不能为null,而且长度必须大于0@NotBlank只能作用在String上,不能为null,而且调用trim()后,长度必须大于0

注意:null和empty的区别

String a = new String

String b = ""

String c = null

此时a是分配了内存空间,但值为空,是绝对的空,是一种有值(值存在为空而已)此时b是分配了内存空间,值为空字符串,是相对的空,是一种有值(值存在为空字串)此时c是未分配内存空间,无值,是一种无值(值不存在)

(2)Boolean校验

注解应用@AssertFalse限制必须为false@AssertTrue限制必须为true

(3)长度校验

注解应用@Size(max,min)验证对象(Array,Collection,Map,String)长度是否在给定的范围之内@Length(min=, max=)验证字符串长度是否在给定的范围之内

(4)日期校验

注解应用@Past限制必须是一个过去的日期,并且类型为java.util.Date@Future限制必须是一个将来的日期,并且类型为java.util.Date@Pattern(value)限制必须符合指定的正则表达式

(5)数值校验

建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null。

注解应用@Min(value)验证 Number 和 String 对象必须为一个不小于指定值的数字@Max(value)验证 Number 和 String 对象必须为一个不大于指定值的数字@DecimalMax(value)限制必须为一个不大于指定值的数字,小数存在精度@DecimalMin(value)限制必须为一个不小于指定值的数字,小数存在精度@Digits(integer,fraction)限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction@Digits验证 Number 和 String 的构成是否合法@Range(max =3 , min =1 , message = " ")Checks whether the annotated value lies between (inclusive) the specified minimum and maximum

注意:Max和Min是对你填的“数字”是否大于或小于指定值,这个“数字”可以是number或者string类型。长度限制用length。

(6)其他校验

注解应用@Email验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email

3、集成方法

3.1、添加依赖:

三种方式添加依赖

javax.validation

validation-api

版本号

org.springframework.boot

spring-boot-starter-web

2.0.5.RELEASE

org.springframework.boot

spring-boot-starter-validation

3.2、实体类中添加相关校验注解

3.3、接口类中添加 @Valid 注解

(1)对象参数

直接在对象前面加@Valid,相关校验注解加在DTO里面

import jakarta.validation.constraints.Max;

import jakarta.validation.constraints.NotBlank;

import lombok.Data;

import org.hibernate.validator.constraints.Length;

import org.springframework.validation.annotation.Validated;

@Data

public class UrlDTO {

@NotBlank(message = "name不能为空")

private String urlName;

@Length(min = 1, max = 10)

private String urlCode;

@Max(5)

private Integer urlType;

}

@RequestMapping("/insert")

public String insert(@Valid UrlDTO urlDTO) {

return "insert OK";

}

@RequestMapping("/add")

public String add(@RequestBody @Valid UrlDTO urlDTO) {

return "add OK";

}

@RequestMapping("/batchAdd")

public String batchAdd( @RequestBody @Valid List urlDTOs) {

return "batch add OK";

}

如果是嵌套的实体对象,则需要在最外层属性上添加 @Valid 注解:

public class User {

@NotBlank(message = "姓名不为空")

private String username;

@NotBlank(message = "密码不为空")

private String password;

//嵌套必须加 @Valid,否则嵌套中的验证不生效

@Valid

@NotNull(message = "用户信息不能为空")

private UserInfo userInfo;

}

(2)非对象参数

直接在参数前面加相关校验注解

(3)集合

在Controller上添加@Validated注解,方法上添加@Valid注解。

3.4、全局异常处理类中处理 @Valid 抛出的异常

全局异常类的处理方式有很多种:

(1)方式一

//异常处理类

@Slf4j

@RestControllerAdvice("com.cbj.db_work.controller") //表明需要处理异常的范围

public class GlobalExceptionHandler {

@ExceptionHandler(value = MethodArgumentNotValidException.class) // 参数异常抛出的异常类型为MethodArgumentNotValidException,这里捕获这个异常

// R为统一返回的处理类

public R validExceptionHandler(MethodArgumentNotValidException e){

System.out.println("数据异常处理");

log.error("数据校验出现问题,异常类型:{}",e.getMessage(),e.getClass());

BindingResult bindingResult = e.getBindingResult();

Map map = new HashMap<>();

bindingResult.getFieldErrors().forEach((item)->{

String message = item.getDefaultMessage();

// 获取错误的属性字段名

String field = item.getField();

map.put(field,message);

});

return R.error().code(CodeEnum.VALID_EXCETIPON.getCode()).message(CodeEnum.VALID_EXCETIPON.getMsg()).data("errorData",map);

}

}

(2)方式二

@ControllerAdvice

@RestControllerAdvice

@Slf4j

public class ValidExceptionHandler extends GlobalExceptionHandler {

// GET请求参数异常处理

@ExceptionHandler(value = ConstraintViolationException.class)

public Result constraintViolationExceptionHandler(ConstraintViolationException e) {

StringBuilder msg = new StringBuilder();

Set> constraintViolations = e.getConstraintViolations();

for (ConstraintViolation constraintViolation : constraintViolations) {

String message = constraintViolation.getMessage();

msg.append(message).append(";");

}

return ResultResponse.getFailResult(ResultCode.BODY_NOT_MATCH.getResultCode(), msg.toString());

}

@ExceptionHandler(ArithmeticException.class)

public Result arithmeticExceptionHandler(ArithmeticException e) {

e.printStackTrace();

return ResultResponse.getFailResult(ResultCode.NOT_FOUND.getResultCode(), "算术异常!"+e.getMessage());

}

// POST请求参数异常处理

@ExceptionHandler(BindException.class)

public Result bindExceptionHandler(BindException e) {

FieldError fieldError = e.getBindingResult().getFieldError();

String msg;

if (Objects.isNull(fieldError)) {

msg = "POST请求参数异常:" + JSON.toJSONString(e.getBindingResult());

log.info(msg);

} else {

msg = fieldError.getDefaultMessage();

}

return ResultResponse.getFailResult(ResultCode.BODY_NOT_MATCH.getResultCode(), msg);

}

}

4、运行流程

(1)get请求:因为 @Valid 不支持平面的参数效验(直接写在参数中字段的效验)所以基于 GET 请求的参数还是按照原先方式进行效验, 而且GET 请求一般接收参数也较少,使用正常判断逻辑进行参数效验即可。

(2) POST请求:则可以以实体对象为参数,可以使用 @Valid 方式进行效验。如果效验通过,则进入业务逻辑,否则抛出异常,交由全局异常处理器进行处理。

5、demo

dto

package com.pluscache.demo.dto;

import jakarta.validation.constraints.Max;

import jakarta.validation.constraints.NotBlank;

import lombok.Data;

import org.hibernate.validator.constraints.Length;

@Data

public class UrlDTO {

@NotBlank(message = "name不能为空")

private String urlName;

@Length(min = 1, max = 10)

private String urlCode;

@Max(5)

private Integer urlType;

}

package com.pluscache.demo.dto;

import lombok.AllArgsConstructor;

import lombok.Data;

import lombok.NoArgsConstructor;

@Data

@AllArgsConstructor

@NoArgsConstructor

public class ResponseMessage {

private int code;

private String message;

private Object data;

}

controller

package com.pluscache.demo.controller;

import com.pluscache.demo.dto.UrlDTO;

import jakarta.validation.Valid;

import jakarta.validation.constraints.Max;

import jakarta.validation.constraints.NotBlank;

import org.springframework.validation.annotation.Validated;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController

@RequestMapping("/url")

@Validated

public class UrlController {

@RequestMapping("/getUser")

public String getUser(@NotBlank String urlName, @Max(5) Integer urlType) {

return "getUser OK";

}

@RequestMapping("/insert")

public String insert(@Valid UrlDTO urlDTO) {

return "insert OK";

}

@RequestMapping("/add")

public String add(@RequestBody @Valid UrlDTO urlDTO) {

return "add OK";

}

@RequestMapping("/batchAdd")

public String batchAdd(@Valid @RequestBody List urlDTOs) {

return "batch add OK";

}

}

全局异常处理

package com.pluscache.demo.hander;

import com.pluscache.demo.dto.ResponseMessage;

import jakarta.validation.ConstraintViolationException;

import lombok.extern.slf4j.Slf4j;

import org.springframework.http.HttpStatus;

import org.springframework.http.converter.HttpMessageNotReadableException;

import org.springframework.validation.BindingResult;

import org.springframework.validation.FieldError;

import org.springframework.web.bind.MethodArgumentNotValidException;

import org.springframework.web.bind.MissingServletRequestParameterException;

import org.springframework.web.bind.annotation.ExceptionHandler;

import org.springframework.web.bind.annotation.ResponseBody;

import org.springframework.web.bind.annotation.ResponseStatus;

import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

@RestControllerAdvice

@Slf4j

public class GlobalExceptionHandler {

/**

* 忽略参数异常处理器

*

* @param e 忽略参数异常

* @return ResponseResult

*/

@ResponseStatus(HttpStatus.BAD_REQUEST)

@ExceptionHandler(MissingServletRequestParameterException.class)

public ResponseMessage parameterMissingExceptionHandler(MissingServletRequestParameterException e) {

log.error("", e);

return new ResponseMessage(600, "请求参数 " + e.getParameterName() + " 不能为空",null);

}

/**

* 缺少请求体异常处理器

*

* @param e 缺少请求体异常

* @return ResponseResult

*/

@ResponseStatus(HttpStatus.BAD_REQUEST)

@ExceptionHandler(HttpMessageNotReadableException.class)

public ResponseMessage parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {

log.error("", e);

return new ResponseMessage(600, "参数体不能为空",null);

}

/**

* 自定义验证异常

* MethodArgumentNotValidException 方法参数无效异常

*/

@ResponseStatus(HttpStatus.BAD_REQUEST) //设置状态码为 400

@ExceptionHandler({MethodArgumentNotValidException.class})

@ResponseBody

public ResponseMessage paramExceptionHandler(MethodArgumentNotValidException ex) {

ResponseMessage responseMessage = new ResponseMessage();

responseMessage.setCode(600);

Map errorsMap = new HashMap<>();

//处理异常

ex.getBindingResult()

.getAllErrors()

.forEach(

(error) -> {

if (error instanceof FieldError) {

String fieldName = ((FieldError) error).getField();

String errorMessage = error.getDefaultMessage();

errorsMap.put(fieldName, errorMessage);

} else {

errorsMap.put(error.getObjectName(), error.getDefaultMessage());

}

});

responseMessage.setData(errorsMap);

return responseMessage;

}

@ResponseStatus(HttpStatus.BAD_REQUEST)

@ExceptionHandler(ConstraintViolationException.class)

@ResponseBody

public ResponseMessage handleValidationExceptions(ConstraintViolationException ex) {

ResponseMessage response = new ResponseMessage();

response.setCode(600);

response.setMessage(ex.getMessage());

return response;

}

}

测试:

(1)普通参数:

localhost:1111/plusDemo/url/getUser?urlName=

(2)对象参数

(3)@RequestBody对象参数

(4)集合参数

(5)嵌套

现在UrlDTO中再加两个对象参数

package com.pluscache.demo.dto;

import jakarta.validation.Valid;

import jakarta.validation.constraints.Max;

import jakarta.validation.constraints.NotBlank;

import lombok.Data;

import org.hibernate.validator.constraints.Length;

import java.util.List;

@Data

public class UrlDTO {

@NotBlank(message = "name不能为空")

private String urlName;

@Length(min = 1, max = 10)

private String urlCode;

@Max(5)

private Integer urlType;

@Valid

UserDTO userDTO;

@Valid

List userDTOList;

}

package com.pluscache.demo.dto;

import com.baomidou.mybatisplus.annotation.TableName;

import jakarta.validation.constraints.Max;

import lombok.Data;

@Data

@TableName("t_user")

public class UserDTO {

private String userName;

private String userAccount;

@Max(30)

private Integer age;

}

访问localhost:1111/plusDemo/url/add

(6)无全局异常处理

现在把全局异常处理去掉,

访问localhost:1111/plusDemo/url/add

后台打印

Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String com.pluscache.demo.controller.UrlController.add(com.pluscache.demo.dto.UrlDTO) with 3 errors: [Field error in object 'urlDTO' on field 'urlName': rejected value []; codes [NotBlank.urlDTO.urlName,NotBlank.urlName,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [urlDTO.urlName,urlName]; arguments []; default message [urlName]]; default message [name不能为空]] [Field error in object 'urlDTO' on field 'urlCode': rejected value [abcfd56hgfh]; codes [Length.urlDTO.urlCode,Length.urlCode,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [urlDTO.urlCode,urlCode]; arguments []; default message [urlCode],10,1]; default message [length must be between 1 and 10]] [Field error in object 'urlDTO' on field 'urlType': rejected value [7]; codes [Max.urlDTO.urlType,Max.urlType,Max.java.lang.Integer,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [urlDTO.urlType,urlType]; arguments []; default message [urlType],5]; default message [must be less than or equal to 5]] ]

二、@Validated注解

1、@Validated 和 @Valid 区别

(1)@Validate 是对@Valid 的封装

(2)@Validate 可以进行分组验证 ,一个对象中都写了验证而你只需要验证该方法需要的验证时使用

2、使用场景

(1)我们使用同一个VO(Request)类来传递save和update方法的数据,但对于id来说,通常有框架帮我们生成id,我们不需要传id此时需要使用注解@Null表名id数据必须为空。但对于update方法,我们必须传id才能进行update操作,所以同一个字段面对不同的场景不同需求就可以使用分组校验。

注意:这种情况,一旦开启了分组校验,就必须把所有的校验规则都指定组别,不然校验也不生效。

(2)上文提到的,配合@Valid做集合对象的校验。

3、demo:

(1)创建分组接口

并不需要实现编写什么代码,标明分类。

//分组接口 1

public interface InsertGroup {

}

//分组接口 2

public class UpdateGroup {

}

(2)DTO:

import com.pluscache.demo.dto.group.InsertGroup;

import com.pluscache.demo.dto.group.UpdateGroup;

import jakarta.validation.Valid;

import jakarta.validation.constraints.Max;

import jakarta.validation.constraints.NotBlank;

import jakarta.validation.constraints.NotNull;

import jakarta.validation.constraints.Null;

import lombok.Data;

import org.hibernate.validator.constraints.Length;

import java.util.List;

@Data

public class UrlDTO {

@Null(message = "无需传id",groups = InsertGroup.class)

@NotNull(message = "必须传入id",groups = UpdateGroup.class)

private Integer id;

@NotBlank(message = "name不能为空")

private String urlName;

@Length(min = 1, max = 10)

private String urlCode;

@Max(5)

private Integer urlType;

@Valid

UserDTO userDTO;

@Valid

List userDTOList;

}

(3)controller

package com.pluscache.demo.controller;

import com.pluscache.demo.dto.UrlDTO;

import com.pluscache.demo.dto.group.InsertGroup;

import com.pluscache.demo.dto.group.UpdateGroup;

import jakarta.validation.Valid;

import jakarta.validation.constraints.Max;

import jakarta.validation.constraints.NotBlank;

import org.springframework.validation.annotation.Validated;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController

@RequestMapping("/url")

@Validated

public class UrlController {

@RequestMapping("/getUser")

public String getUser(@NotBlank String urlName, @Max(5) Integer urlType) {

return "getUser OK";

}

@RequestMapping("/insert")

public String insert(@Validated(InsertGroup.class) UrlDTO urlDTO) {

return "insert OK";

}

@RequestMapping("/update")

public String update(@Validated(UpdateGroup.class) UrlDTO urlDTO) {

return "update OK";

}

@RequestMapping("/add")

public String add(@RequestBody @Valid UrlDTO urlDTO) {

return "add OK";

}

@RequestMapping("/batchAdd")

public String batchAdd(@Valid @RequestBody List urlDTOs) {

return "batch add OK";

}

}

(4)测试

而访问update接口,不加id校验不通过,加上id则成功

三、自定义校验注解