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

一、@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
  1. 此时a是分配了内存空间,但值为空,是绝对的空,是一种有值(值存在为空而已)
  2. 此时b是分配了内存空间,值为空字符串,是相对的空,是一种有值(值存在为空字串)
  3. 此时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、添加依赖:

三种方式添加依赖

			<!--第一种:valid依赖-->
		<dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<version>版本号</version>
		</dependency>

		<!--		第二种:集成于web依赖中(注意版本号)-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<version>2.0.5.RELEASE</version>
		</dependency>

		<!--		第三种:springboot的validation-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>
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<UrlDTO> 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<String,String> 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<Object> constraintViolationExceptionHandler(ConstraintViolationException e) {
        StringBuilder msg = new StringBuilder();
        Set<ConstraintViolation<?>> 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<Object> arithmeticExceptionHandler(ArithmeticException e) {
        e.printStackTrace();
        return ResultResponse.getFailResult(ResultCode.NOT_FOUND.getResultCode(), "算术异常!"+e.getMessage());
    }

    // POST请求参数异常处理
    @ExceptionHandler(BindException.class)
    public Result<Object> 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<UrlDTO> 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<String, String> 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<UserDTO> 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<UserDTO> 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<UrlDTO> urlDTOs) {
        return "batch add OK";
    }
}
(4)测试

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

三、自定义校验注解

  • 18
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: @Valid注解和@Validated注解都是用于校验数据的注解,但是它们的使用场景略有不同。 @Valid注解是JSR-303规范中定义的注解,用于校验JavaBean中的属性值是否符合规范。它可以用在方法参数、方法返回值、方法参数中的属性、方法返回值中的属性等位置上。在Spring Boot中,我们可以在Controller中使用@Valid注解来校验请求参数是否符合规范。 @Validated注解是Spring框架中提供的注解,它可以用于校验方法参数是否符合规范。它支持分组校验,可以根据不同的场景对参数进行不同的校验。在Spring Boot中,我们可以在Service层或者Controller中使用@Validated注解来校验方法参数是否符合规范。 总的来说,@Valid注解用于校验JavaBean中的属性值,而@Validated注解用于校验方法参数。两者都可以用于校验数据,但是使用场景略有不同。 ### 回答2: @Valid注解和@Validated注解都用于校验数据的有效性,但是它们有所不同。 @Valid注解是来自于javax.validation规范中的注解,是用于Bean Validation校验的,它可以用于校验Java Bean对象中的字段。@Valid注解只能加在某个目标类型参数上,并且该参数类型需要是一个对象,它可以递归验证对象中的所有字段和对象。 @Validated注解则是Spring框架提供的注解,是用于Spring校验的,它可以用于校验方法和参数。@Validated注解用于验证参数和返回值,通常放在接口方法上。@Validated注解描述的约束条件必须是JSR303支持的约束条件的JavaBean,而@Valid注解不支持约束条件。 总的来说,@Valid注解是制定了JSR303规范而来的,属于Bean Validation更多的是参数校验,而@Validated注解是Spring框架专门提供的注解,用于控制层的校验,其作用与@Valid注解类似,但更加灵活,能进行更多的分组约束、级联验证等。 ### 回答3: @Valid注解和@Validated注解是Java中用于数据校验的两个注解。它们都是基于Bean Validation规范的,提供了对Java Bean属性进行校验的功能。 @Valid注解用于进行简单的数据校验,它可以在Controller层的方法参数中对参数进行校验。它的作用是告诉Spring框架对该参数进行数据校验,如果校验失败,则会抛出ConstraintViolationException异常。该注解使用时需要配合bindingResult参数一起使用,bindingResult参数用于接收校验结果。 @Validated注解更加强大,除了提供了@Valid注解的功能外,它还可以在Service层和Dao层对方法参数进行校验。它支持Spring Expression Language(SpEL)表达式,并提供了基于分组校验的功能。基于分组校验可以根据不同的校验场景,对同一个实体进行不同的校验,增强了数据校验的灵活性。 除了以上的差异,还有一个重要区别:@Valid注解是标准的Java注解,而@Validated注解是Spring提供的注解。因此,如果项目中只使用了Spring框架,那么可以直接使用@Validated注解。但如果使用的是标准的Java环境,则需要使用@Valid注解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

w_t_y_y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值