数据校验很重要,在前端对数据进行校验的情况下,我们还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 HTTP 工具直接向后端请求一些违法数据。
虽然我们通过 if/else 语句对请求的每一个参数一一校验。但是这样写起来太麻烦了,而且破坏了单一职责原则。所以今天我们来看一下 Spring Boot validation 。
首先我们先来看两个注解
1. @Validated和@Valid 的区别
@Validated: 可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上; 用在方法入参上无法单独提供嵌套验证功能;能配合嵌套验证注解 @Valid 进行嵌套验证。
@Valid: 可以用在方法、构造函数、方法参数和成员属性(字段)上; 用在方法入参上无法单独提供嵌套验证功能;能够用在成员属性(字段)上,提示验证框架进行嵌套验证;;能配合嵌套验证注解 @Valid 进行嵌套验证。
ex1:
package com.scaffold.test.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@Data
@EqualsAndHashCode(callSuper = false)
public class Mate implements Serializable {
private static final long serialVersionUID=1L;
@NotBlank(message = "小伙伴的name不能为空")
private String name;
@NotNull(message = "小伙伴的age不能为空")
private Integer age;
}
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.validator.constraints.Range;
import org.springframework.data.annotation.Id;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = false)
public class Student implements Serializable {
private static final long serialVersionUID=1L;
@Id
@Range(min = 1, message = "id不能为空")
private int id;
@NotBlank(message = "name不能为空")
private String name;
@NotNull(message = "age不能为空")
private Integer age;
@Valid
@NotNull(message = "mateList不能为空")
@Size(min = 1, message = "至少需要一个小伙伴")
private List<Mate> mateList;
}
@PostMapping("post")
public Result postStudent(@Validated @RequestBody Student student) {
return ResultGenerator.setSuccessResult(student);
}
除此之外, @Validated:支持分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制; @Valid:不支持分组;
ex2:当更新一个Student时需要校验ID,当新增一个Student时,不需要校验ID,所以这种情况,需要有不同的验证机制。
定义两个接口
import javax.validation.groups.Default;
public interface Insert extends Default {
}
import javax.validation.groups.Default;
public interface Update extends Default {
}
然后后在需要校验的字段上加入分组:
@Id
@Range(min = 1, message = "id不能为空", groups = Update.class)
private int id;
最后根据需要,在Controller处理请求中加入@Validated注解并引入需要校验的分组
@PostMapping("post")
public Result postStudent(@Validated(Insert.class) @RequestBody Student student) {
return ResultGenerator.setSuccessResult(student);
}
@PostMapping("update")
public Result updateStudent(@Validated(Update.class) @RequestBody Student student) {
return ResultGenerator.setSuccessResult(student);
}
2. 常用校验注解总结
JSR 提供的校验注解:
Hibernate Validator 提供的校验注解:
3.使用validation
如果我们不使用数据校验的话,最普通的方法就是使用if- else 语句进行校验。
@RestController
@RequestMapping("/api/person")
public class PersonController {
@PostMapping
public ResponseEntity<PersonRequest> save(@RequestBody PersonRequest personRequest) {
if (personRequest.getClassId() == null
|| personRequest.getName() == null
|| !Pattern.matches("(^Man$|^Woman$|^UGM$)", personRequest.getSex())) {
}
return ResponseEntity.ok().body(personRequest);
}
}
如果开发普通 Java 程序的的话,你需要可能需要像下面这样依赖:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.9.Final</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.6</version>
</dependency>
Spring Boot 2.3 1 之后,spring-boot-starter-validation 已经不包括在了 spring-boot-starter-web 中,需要我们手动加上。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
3.1验证 Controller 的输入
验证请求体 验证请求体即使验证被 @RequestBody 注解标记的方法参数。
我们在需要验证的参数上加上了@Valid注解,如果验证失败,它将抛出MethodArgumentNotValidException。默认情况下,Spring 会将此异常转换为 HTTP Status 400(错误请求)。
controller
@RestController
@RequestMapping("/api/person")
@Validated
public class PersonController {
@PostMapping
public ResponseEntity<PersonRequest> save(@RequestBody @Valid PersonRequest personRequest) {
return ResponseEntity.ok().body(personRequest);
}
}
entity 我们使用校验注解对请求的参数进行校验!
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PersonRequest {
@NotNull(message = "classId 不能为空")
private String classId;
@Size(max = 33)
@NotNull(message = "name 不能为空")
private String name;
@Pattern(regexp = "(^Man$|^Woman$|^UGM$)", message = "sex 值不在可选范围")
@NotNull(message = "sex 不能为空")
private String sex;
}
@RestController
@RequestMapping("/api")
public class ExceptionController {
@GetMapping("/illegalArgumentException")
public void throwException() {
throw new IllegalArgumentException();
}
@GetMapping("/resourceNotFoundException")
public void throwException2() {
throw new ResourceNotFoundException();
}
}
验证请求参数 验证请求参数(Path Variables 和 Request Parameters)即是验证被 @PathVariable 以及 @RequestParam 标记的方法参数。
在类上加上 Validated 注解了,这个参数可以告诉 Spring 去校验方法参数。
@RestController
@RequestMapping("/api/persons")
@Validated
public class PersonController {
@GetMapping("/{id}")
public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5, message = "超过 id 的范围了") Integer id) {
return ResponseEntity.ok().body(id);
}
@PutMapping
public ResponseEntity<String> getPersonByName(@Valid @RequestParam("name") @Size(max = 6, message = "超过 name 的范围了") String name) {
return ResponseEntity.ok().body(name);
}
}
@ExceptionHandler(ConstraintViolationException.class)
ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
验证 Service 中的方法 我们还可以验证任何 Spring Bean 的输入,而不仅仅是 Controller 级别的输入。通过使用@Validated和@Valid注释的组合即可实现这一需求!
在类上加上 Validated 注解了,这个参数可以告诉 Spring 去校验方法参数。
@Service
@Validated
public class PersonService {
public void validatePersonRequest(@Valid PersonRequest personRequest) {
}
}
3.自定义Validation 注解类(实用)
校验特定字段的值是否在可选范围 比如我们现在多了这样一个需求:PersonRequest 类多了一个 Region 字段,Region 字段只能是China、China-Taiwan、China-HongKong这三个中的一个。
①建一个注解 Region。
@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = RegionValidator.class)
@Documented
public @interface Region {
String message() default "Region 值不在可选范围内";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
②实现 ConstraintValidator接口,并重写isValid 方法。
public class RegionValidator implements ConstraintValidator<Region, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
HashSet<Object> regions = new HashSet<>();
regions.add("China");
regions.add("China-Taiwan");
regions.add("China-HongKong");
return regions.contains(value);
}
}
③使用这个注解
@Region
private String region;
|