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
Int
、Integer
不能使用正则- 正则只能放在
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、总结
上文都是自己日常开发的时候踩了一些坑,或者自己开发需要注意的地方,希望记录下来,警醒自己,当然正如本山大叔所说的,错了就改,改了再犯,千锤百炼嘛。