在项目开发中,时常会碰到前端传递过来的请求参数需要校验,毕竟永远不要相信没有经过自己校验的数据,如果是零星几个参数,直接 if...else if ...else...
即可,但数据量大了,同时为了尽可能地增加复用,这里就可以用到参数校验
了,如果你觉得框架提供的校验方法不够用,或者你的校验比较个性化,那就自定义校验
。
环境:
- Spring Boot:3.1.6
- JDK:17
声明:
接下来的内容主要基于 [1] 做一定改动,如果想看原文,请点击原文,链接在下面参考中。
1.主要注解
先看常用注解有哪些:
可以看到注解主要集中于以下几个方面:
- 针对时间的,以前,现在,未来
- 针对数值型的,最小,最大,正负情况
- 针对字符串的,长度大小,是否空
- 针对布尔值的,true or false
以下是主要注解:
-
@NotNull
:值不能为null; -
@NotEmpty
:字符串、集合或数组的值不能为空,即长度大于0; -
@NotBlank
:字符串的值不能为空白,即不能只包含空格; -
@Size
:字符串、集合或数组的大小是否在指定范围内; -
@Min
:数值的最小值; -
@Max
:数值的最大值; -
@DecimalMin
:数值的最小值,可以包含小数; -
@DecimalMax
:数值的最大值,可以包含小数; -
@Digits
:数值是否符合指定的整数和小数位数; -
@Pattern
:字符串是否匹配指定的正则表达式; -
@Email
:字符串是否为有效的电子邮件地址; -
@AssertTrue
:布尔值是否为true; -
@AssertFalse
:布尔值是否为false; -
@Future
:日期是否为将来的日期; -
@Past
:日期是否为过去的日期;
2.注解使用
创建项目&&添加依赖
首先肯定还是先创建一个 Spring Boot web 项目,因为我们会用到参数校验,这里需要在 pom.xml 添加三方包依赖:
<!-- params validate -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
我们这里假设一种用户场景,后端根据前端提交过来的用户参数做校验,校验通过后,存入数据库中(项目演示为主,忽略数据库的使用),如果校验失败,将失败信息返回给前端。
校验的用户类
package com.example.springbootparamvalidatedemo.param;
import com.example.springbootparamvalidatedemo.util.Phone;
import jakarta.validation.constraints.*;
import lombok.Data;
@Data
public class User {
@NotBlank(message = "用户名不能为空")
private String name;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 30, message = "密码长度在6到30之间")
private String password;
@Min(value = 18, message = "必须成年")
@Max(value = 120, message = "不得超过年龄极限")
private int age;
@Pattern(regexp = "^(18[0-9])\d{8}$", message = "格式不正确")
private String phone;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
在用户类中,我们仅通过几个基本字段,根据字段属性,添加一定的校验注解。
统一的响应体
通常在项目开发中,我们都会用到统一的响应数据格式,所以这里将响应格式封装后,作为工具类供调用。
package com.example.springbootparamvalidatedemo.util;
import lombok.Data;
import java.io.Serializable;
@Data
public class Resp<T> implements Serializable {
private int code;
private boolean success;
private T data;
private String msg;
private Resp(int code, T data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
this.success = code == 200;
}
public static <T> Resp<T> ok(T data) {
return new Resp<>(200, data, null);
}
public static <T> Resp<T> error(String msg) {
return new Resp<>(500, null, msg);
}
}
上面的响应体中,主要简单分为 成功和失败 的两种情况,考虑到通用,我们引入 泛型。
controller类中应用
这里就是简单根据前端传入的用户参数做校验,然后返回响应。
package com.example.springbootparamvalidatedemo.controller;
import com.example.springbootparamvalidatedemo.param.User;
import com.example.springbootparamvalidatedemo.util.Resp;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/save")
public Resp save(@Valid @RequestBody User user) { // @Valid 表示校验参数
return Resp.ok(user);
}
}
为了让项目更加完善,引入全局异常处理,对最后的响应做拦截输出。
全局异常处理
package com.example.springbootparamvalidatedemo.exception;
import com.example.springbootparamvalidatedemo.util.Resp;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler
public Resp handleError(BindException e) {
BindingResult bindingResult = e.getBindingResult();
return Resp.error(bindingResult.getFieldError().getDefaultMessage());
}
}
校验演示
我们启动项目,然后在 postman 中测试看看。
这里假定传入了 age 不合规定的值:
传入全部合规的值:
从上面结果来看,参数校验是可以用的。
3.自定义注解
自定义实现
尽管框架提供一些校验规则,难免遇到一些现有规则不能覆盖的情况,这里我们就一些特定情况做个自定义的参数校验。
我们可以先观察下现有校验规则是怎样的:
Min
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Min.List.class)
@Documented
@Constraint(
validatedBy = {}
)
public @interface Min {
String message() default "{jakarta.validation.constraints.Min.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
long value();
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface List {
Min[] value();
}
}
NotBlank
@Documented
@Constraint(
validatedBy = {}
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(NotBlank.List.class)
public @interface NotBlank {
String message() default "{jakarta.validation.constraints.NotBlank.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface List {
NotBlank[] value();
}
}
理论上说,我们写个类似的注解接口,是不是就可以用呢?
实际上,在做自定义的参数校验的时候,除了自定义的注解接口,另外我们还需要再实现一个接口 ConstraintValidator
。
我们在 User 类中加个参数,比如加个用户地区,限定 Asia 或 Ameraica
,只有这两个地区的人才可以注册。
下面开始我们的自定义编码。
Address
package com.example.springbootparamvalidatedemo.util;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {AddressValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD,
ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR,
ElementType.PARAMETER, ElementType.TYPE_USE})
public @interface Address {
String message() default "不在合法地区范围内";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
实现类
package com.example.springbootparamvalidatedemo.util;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.util.Arrays;
public class AddressValidator implements ConstraintValidator<Address, String> {
private static String[] addresses = {"Asia", "America"};
@Override
public boolean isValid(String address, ConstraintValidatorContext context) {
if (Arrays.asList(addresses).contains(address)) {
return true;
}
return false;
}
}
在 User类 中加入 address 字段:
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@NotBlank(message = "地址非空")
@Address
private String address;
测试:
address 不在范围内的
address 在范围内
可以看到这里是生效了的。
自定义要点
注解接口:
-
@Constraint(validatedBy = {PhoneValidator.class})
:用于指定验证器类; -
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
:指定@Phone
注解可以作用在方法、字段、构造函数、参数以及类型上
实现类[2]:
想让自定义验证注解生效,需要实现 ConstraintValidator
接口。接口的第一个参数是 自定义注解类型,第二个参数是 被注解字段的类型。这里因为订单ID 是 String 类型,我们第二个参数定义为 String 就可以了,需要提到的一点是 ConstraintValidator
接口的实现类无需添加 @Component
它在启动的时候就已经被加载到容器中了。
参考:
标签:Java,String,自定义,校验,import,ElementType,public From: https://www.cnblogs.com/davis12/p/17879793.html