SpringBoot请求参数校验@Valide和@Validated区别以及内部类参数校验失效怎么处理?

SpringBoot请求参数校验@Valide和@Validated区别以及内部类参数校验失效怎么处理?

1、Get请求

一般get 请求我们都是使用@RequestParam注解,后面常见的参数是@RequestParam(defaultValue = "1")或者@RequestParam(required = false,这里面其中@Valide@Validated一般都是验证post 请求和put请求等,这个时候我们一般都是使用dto这个时候才有验证的必要

2、Post请求

@Valid

  • 来源: @Valid 是 JSR 303/JSR 349 (Bean Validation 1.0/1.1) 规范的一部分。
  • 用途: 主要用于启用 Bean Validation API 对 JavaBean 对象的属性进行校验。
  • 应用范围: 可用于类的字段、方法参数、方法返回值等。
  • 限制: 不支持分组验证,也就是说,不能根据不同场景应用不同的验证规则。
  • 集成: 与 Spring 集成时,Spring 将 @Valid 视为一个标准的 JSR-303 注解,用于触发验证逻辑。
  • 适用场景: 当你需要验证一个实体类的属性,且没有复杂的分组需求时,@Valid 是一个简单且合适的选择。

@Validated(推荐)

  • 来源: @Validated 是 Spring 特定的注解。
  • 用途: 除了支持 @Valid 的基本功能外,还增加了分组验证的功能。
  • 应用范围: 通常用于类级别和方法级别。可以应用于 Spring MVC 控制器的方法参数上。
  • 特性: 支持分组验证,允许在验证时指定应用哪些验证规则。这对于同一个对象在不同场景下有不同验证规则的情况特别有用。
  • 集成: 它是 Spring 框架的一部分,并且与 Spring 的验证机制紧密集成。
  • 适用场景: 当你需要更复杂的验证逻辑,例如在创建和更新场景中应用不同的验证规则时,@Validated 是更好的选择。

相同点

  • 目的: 两者都旨在提供方式来触发验证逻辑,确保对象满足特定的约束条件。
  • 使用方式: 通常与实体类或 DTO (Data Transfer Object)配合使用,通过注解来指定验证规则。
  • 应用场景: 两者都广泛用于 Web 应用中,特别是在处理 HTTP 请求和响应时。

总结

虽然 @Valid@Validated 都用于数据验证,但 @Validated 提供了更多的灵活性,特别是在需要进行分组验证的场景中。在简单的场景下,@Valid 可能就足够了。但如果你的项目使用 Spring,并且你需要更复杂的验证逻辑,那么 @Validated 是更好的选择。

3、示例

我以我最近做的一个问卷调查的代码作为例子来展示具体使用方法

@PostMapping("/submit")
    public BaseResponse<Boolean> submitSurveyAnswers(@Validated @RequestBody SubmitSurveyRequest submitSurveyRequest) {
        if (submitSurveyRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Boolean surveyResults = quesSurveyResultsService.getSurveyResults(submitSurveyRequest);
        if (!surveyResults) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "保存失败,请联系管理员");
        }
        return ResultUtils.success(null, "保存成功,请使用手机号查询结果");
    }

DTO(含内部类)

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
import java.util.List;
//上面有注解的来源,看看从哪个包来的
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SubmitSurveyRequest implements Serializable {
    private static final long serialVersionUID = 1L;

    @NotNull(message = "参数有误,答案列表不能为空")
  	//这里需要解释一下:因为我里面有内部类,这里面仍然有自定义注解,所以在这个answers上面要加一个注解,不然内部类的属性不生效
    @Valid
    private List<Answer> answers;
    @NotNull(message = "参数有误,手机号不能为空")
    @Pattern(regexp = "^[0-9]+$", message = "手机号格式错误,只能包含数字")
    private String phone;
    //这里注意一下,正则在int类型是不生效的,只能字符串,如果必须有,使用业务代码校验,不能使用正则校验,不然会导致其他正则也失效
    private String quesActId;

    /**
     * 答案类,表示问卷中的一个答案。
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Answer {

        @NotNull(message = "参数有误,题号不能为空")
        private int id;
        @NotNull(message = "参数有误,答案不能为空")
        private Object answer; // 单选或填空答案

        private SubAnswer subAnswer; // 子问题答案
    }

    /**
     * 子答案类,表示主问题答案的子问题答案。
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class SubAnswer {
        private double id;
        private String answer;
    }
}

注意:

  • 如果一个属性有内部类,内部类的属性有校验,在外面需要加注解@Valid
  • IntInteger不能使用正则
  • 正则只能放在String属性上
@NotNull(message = "参数有误,活动ID不能为空")
@Pattern(regexp = "^\\d{4}$", message = "活动ID格式错误,必须是四位数字")
private String quesActId;

4、自定义异常

违反了我们自定义的注解了,之后呢?我们需要自定义异常捕捉我们的异常

/**
     * 处理方法参数验证异常。
     * 当控制器方法中的请求参数验证失败时,Spring MVC 会抛出 MethodArgumentNotValidException 异常。
     * 此方法捕获该异常,并从异常中提取具体的验证错误信息,将其转换为用户友好的格式。
     *
     * @param e 捕获到的 MethodArgumentNotValidException 对象,包含关于验证失败的详细信息。
     * @return BaseResponse 对象,包含错误代码和错误信息。错误信息为验证失败的具体原因,如字段验证不通过的消息。
     * @author lizhiwei
     * @date 2023/12/25 14:44
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public BaseResponse<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        // 从异常中提取错误信息
        BindingResult result = e.getBindingResult();
        List<FieldError> fieldErrors = result.getFieldErrors();
        String errorMessage = fieldErrors.stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining(", "));
				//除了捕捉的异常之外,要按照公司的规范来记录日志,方便线上有问题的时候定位问题,日志要重视!
        log.error("记录日志:" + "MethodArgumentNotValidException: " + errorMessage);
        return ResultUtils.error(ErrorCode.PARAMS_ERROR, errorMessage);
    }

//这个需要放在你的GlobalExceptionHandler里面,比如:下面的----------------------------------
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
  @ExceptionHandler(MethodArgumentNotValidException.class)
    public BaseResponse<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        // 从异常中提取错误信息
        BindingResult result = e.getBindingResult();
        List<FieldError> fieldErrors = result.getFieldErrors();
        String errorMessage = fieldErrors.stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining(", "));

        log.error("记录日志:" + "MethodArgumentNotValidException: " + errorMessage);
        return ResultUtils.error(ErrorCode.PARAMS_ERROR, errorMessage);
    }
}

5、总结

上文都是自己日常开发的时候踩了一些坑,或者自己开发需要注意的地方,希望记录下来,警醒自己,当然正如本山大叔所说的,错了就改,改了再犯,千锤百炼嘛。

  • 30
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值