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 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步