Spring 参数校验

在前端对数据进行校验的情况下,在后端还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 HTTP 工具直接向后端请求一些违法数据。

JSR(Java Specification Requests) 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们 JavaBean 的属性上面,这样就可以在需要校验的时候很方便地进行校验了!

实际校验的时候,我们用的是 Hibernate Validator 框架。

Hibernate Validator 是 Hibernate 团队最初的数据校验框架:

  • Hibernate Validator 4.x 是 Bean Validation 1.0(JSR 303)的参考实现;

  • Hibernate Validator 5.x 是 Bean Validation 1.1(JSR 349)的参考实现;

  • Hibernate Validator 6.x 是 Bean Validation 2.0(JSR 380)的参考实现,是目前最新版的。

SpringBoot 项目的 spring-boot-starter-web 依赖中已经有 hibernate-validator 包,不需要引用相关依赖。

JSR提供的校验注解:

  • @Null:被注释的元素必须为 null

  • @NotNull:被注释的元素必须不为 null

  • @AssertTrue:被注释的元素必须为 true

  • @AssertFalse:被注释的元素必须为 false

  • @Min(value):被注释的元素必须是一个数字,其值必须大于等于指定的最小值

  • @Max(value):被注释的元素必须是一个数字,其值必须小于等于指定的最大值

  • @DecimalMin(value):被注释的元素必须是一个数字,其值必须大于等于指定的最小值

  • @DecimalMax(value):被注释的元素必须是一个数字,其值必须小于等于指定的最大值

  • @Size(max=, min=):被注释的元素的大小必须在指定的范围内

  • @Digits (integer, fraction):被注释的元素必须是一个数字,其值必须在可接受的范围内

  • @Past:被注释的元素必须是一个过去的日期

  • @Future:被注释的元素必须是一个将来的日期

  • @Pattern(regex=,flag=):被注释的元素必须符合指定的正则表达式

  • ...

Hibernate Validator 提供的校验注解:

  • @NotBlank(message =):验证字符串非null,且长度必须大于0

  • @Email:被注释的元素必须是电子邮箱地址

  • @Length(min=,max=):被注释的字符串的大小必须在指定的范围内

  • @NotEmpty:被注释的字符串的必须非空

  • @Range(min=,max=,message=):被注释的元素必须在合适的范围内

需要注意的是:所有的注解,推荐使用 JSR 注解,即 javax.validation.constraints,而不是 org.hibernate.validator.constraints

依赖

导入 maven 的依赖:

<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>

验证方式

验证 Controller 的输入

验证请求体:RequestBody

我们需要在待验证的参数上加上 @Valid 注解,如果验证失败,它将抛出 MethodArgumentNotValidException。默认情况下,Spring 会将此异常转换为 HTTP Status 400(错误请求)。

【示例】

@RestController
@RequestMapping("/api")
publicclass PersonController {
@PostMapping("/person")
public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) {
return ResponseEntity.ok().body(person);
}
}

通常,我们需要一个全局异常处理器,帮助我们捕获未被处理的异常:

@ControllerAdvice(assignableTypes = {PersonController.class})
publicclass GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors);
}
}

验证请求参数:Path Variables 和 Request Parameters

此时,我们需要在 Controller 上加上 @Validated 注解,这样才能使 Spring 校验方法参数。

@RestController
@RequestMapping("/api")
@Validated
publicclass PersonController {
@GetMapping("/person/{id}")
public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5,message = "超过 id 的范围了") Integer id) {
return ResponseEntity.ok().body(id);
}
@PutMapping("/person")
public ResponseEntity<String> getPersonByName(@Valid @RequestParam("name") @Size(max = 6,message = "超过 name 的范围了") String name) {
return ResponseEntity.ok().body(name);
}
}

ExceptionHandler

@ControllerAdvice(assignableTypes = {PersonController.class})
publicclass GlobalExceptionHandler {
@ExceptionHandler(ConstraintViolationException.class)
ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
}

校验 Service

只需要在 Service 类上加上 @Validated 注解即可:

@Service
@Validated
publicclass PersonService {
public void validatePerson(@Valid Person person){
// do something
}
}

Validator 编程方式手动进行参数验证

非 spring 项目中,通过 Validator 工厂类获得 Validator 实例,手动校验:

public void check_person_manually() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Person person = new Person();
person.setSex("Man22");
person.setClassId("82938390");
person.setEmail("SnailClimb");
Set<ConstraintViolation<Person>> violations = validator.validate(person);
//output:
//email 格式不正确
//name 不能为空
//sex 值不在可选范围
for (ConstraintViolation<Person> constraintViolation : violations) {
System.out.println(constraintViolation.getMessage());
}
}

在 spring 项目中,我们也可以通过 @Autowired 直接注入的方式。

@Autowired
Validator validate

自定义 Validator

校验字段范围

【场景】:对于 region 字段只能是 China、China-Taiwan、China-HongKong 这三个中的一个。

接下来,我们按照下面的步骤,就可以实现一个自定义注解校验。

  • 步骤一:创建一个枚举类
enum Region {
CHINA("China"), TAIWAN("China-Taiwan"), HONGKONG("China-HongKong");
private final String value;
Region(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
  • 步骤二:创建一个注解
@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = EnumValidator.class)
@Documented
public @interface EnumSelector {
Class<? extends Enum<?>> clazz();
String message() default "the value should be the enumerate value in the class {clazz}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
  • 步骤三:实现 ConstraintValidator 接口,并重写 isValid 方法
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
public class EnumValidator implements ConstraintValidator<EnumSelector, Object> {
private List<String> values;
@Override
public void initialize(EnumSelector enumSelector) {
values = Stream.of(enumSelector.clazz().getEnumConstants()).map(Enum::toString).collect(Collectors.toList());
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (Objects.isNull(value)) {
return true;
}
return values.contains(value.toString());
}
}
  • 步骤四:使用注解:
@EnumSelector(clazz= Region.class)
@NotNull
private String region;

验证组

某些场景下我们需要使用到验证组,例如,对资源操作的不同方法,有不同的验证规则。

  • 先创建三个接口
public interface QueryUser {
}
public interface AddUser {
}
public interface DeleteUser {
}
  • 使用验证组
class User {
@NotNull(groups = {AddUser.class, DeleteUser.class})
private String name;
@NotNull(groups = {QueryUser.class, DeleteUser.class})
private String userId;
...
}
  • 校验
@Service
@Validated
publicclass UserService {
@Validated(AddUser.class)
public void addUser(@Valid User user) {
// do something
}
@Validated(QueryUser.class)
public void getUser(@Valid User user) {
// do something
}
@Validated(DeleteUser.class)
public void deleteUser(@Valid User user) {
// do something
}
}

使用验证组这种方式的时候一定要小心,这是一种反模式,还会造成代码逻辑性变差。


参考:

本文作者:LARRY1024

本文链接:https://www.cnblogs.com/larry1024/p/17775005.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   LARRY1024  阅读(379)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.