Springboot 参数校验
? Java API 规范(JSR303 )定义了Bean 校验的标准 validation-api ,但没有提供实现。hibernate validation 是对这个规范的实现,并增加了校验注解如 @Email 、@Length 等。
? Spring Validation 是对 hibernate validation 的二次封装,用于支持 spring mvc 参数自动校验。
引入依赖
? 如果spring-boot 版本小于2.3.x ,spring-boot-starter-web 会自动传入hibernate-validator 依赖。如果spring-boot 版本大于2.3.x ,则需要手动引入依赖:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.1.Final</version>
</dependency>
requestBody 参数校验
? POST 、PUT 请求一般会使用requestBody 传递参数,这种情况下,只要给 DTO 对象加上 @Validated 注解就能实现自动参数校验。
? 比如,有一个保存Brand 的接口,要求 name 长度是不能超过30字符。
@Data
public class BrandDto {
private Long id;
@NotBlank(message = "品牌名不能为空!")
@Length(max = 30, message = "品牌名太长了!")
private String name;
private String chineseSpell;
private String englishName;
}
@PostMapping("/brand")
public Result saveBrand(@RequestBody @Valid BrandDto brand){
return brandService.saveBrand(brand);
}
? 校验失败,会抛出 MethodArgumentNotValidException 异常,Spring 默认会将其转为400(Bad Request) 请求。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y137UAG6-1637506371219)(https://i.loli.net/2021/11/21/iq2us3R7968ItVg.png)]
?
requestParam/PathVariable 参数校验
? 如果是 GET 请求一般会使用 requestParam/PathVariable 传参,此时要在类上标注 @Validated 注解,并在入参上声明约束注解。
@RestController
@RequestMapping("/v1/web/brand")
@Validated
public class BrandController {
@Autowired
private BrandService brandService;
@GetMapping("/brand")
public Result getBrand(@RequestParam("id")
@NotNull(message = "id不能为空!")
@Max(value = 10000, message = "id超过最大范围了!") Long id){
return brandService.getBrand(id);
}
}
? 校验失败,会抛出 ConstraintViolationException 异常:
?
统一异常处理
? 如果校验失败,会抛出 MethodArgumentNotValidException 或者 ConstraintViolationException 异常。在实际项目开发中,通常会用统一异常处理根据业务情况来返回一个更友好的提示:
@RestControllerAdvice
public class GlobalExectionHandler {
@ExceptionHandler({MethodArgumentNotValidException.class})
public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException exception){
BindingResult bindingResult = exception.getBindingResult();
List<FieldError> fieldErrorList = bindingResult.getFieldErrors();
int size = fieldErrorList.size();
StringBuilder sb = new StringBuilder("参数校验失败:");
for (int i = 0; i < size; i++) {
FieldError fieldError = fieldErrorList.get(i);
sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage());
if(i != (size-1)){
sb.append(", ");
}
}
String msg = sb.toString();
return Result.parameterError().setMessage(msg);
}
@ExceptionHandler({ConstraintViolationException.class})
public Result handleConstraintViolationException(ConstraintViolationException exception) {
return Result.parameterError().setMessage("参数校验失败:" + exception.getMessage());
}
}
快速失败
? Spring Validation 默认会校验完所有字段,然后才抛出异常。可以开启 Fali Fast 模式,一旦校验失败就立即返回。
@Configuration
public class ValidateConfig {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}
嵌套校验
? 实际场景中,可能存在一个实体类中还嵌套着另外的实体类,即一个对象的某个属性是对另一个对象的引用。这种情况下可以使用嵌套校验 ,此时,该属性字段上必须加上 @Valid 注解,下面是一个具体的例子:
@Data
public class BrandDto {
private Long id;
@NotBlank(message = "品牌名不能为空!")
@Length(max = 30, message = "品牌名太长了!")
private String name;
private String chineseSpell;
private String englishName;
@Valid
private EnterpriseBrandDto enterpriseBrandDto;
}
@Data
public class EnterpriseBrandDto {
private Long id;
@NotNull(message = "企业账号id不能为空!")
private Long enterpriseId;
@NotNull(message = "品牌id不能为空!")
private Long brandId;
private Date createDate;
private Date updateDate;
}
@RestController
@RequestMapping("/v1/web/brand")
public class BrandController {
@PostMapping("/brand")
public Result saveBrand(@RequestBody @Valid BrandDto brand){
return brandService.saveBrand(brand);
}
}
集合校验
? 如果请求体直接传递了json 数组给后台,并希望对数组中的每一项都进行参数校验。此时要使用自定义 list 集合来接收参数:
@Data
public class ValidList<E> implements List<E> {
@Delegate
@Valid
public List<E> list = new ArrayList<>();
}
@RestController
@RequestMapping("/v1/web/brand")
public class BrandController {
@PostMapping("/brands")
public Result saveBrands(@RequestBody @Valid ValidList<BrandDto> brands){
return brandService.saveBrands(brands);
}
}
|