SpringBoot 集成参数校验
第一步:引入依赖
注:从 springboot-2.3开始,校验包被独立成了一个 starter组件,所以需要引入validation和web,而 springboot-2.3之前的版本只需要引入 web 依赖就可以了。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--参数校验依赖2.3开始需单独引入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
第二步:定义要参数校验的实体类
@Data
public class ValidVO {
private String id;
@NotNull(message = "appId不能为空")
@Length(min = 6, max = 12, message = "appId长度必须位于6到12之间")
private String appId;
@NotBlank(message = "名字为必填项")
private String name;
@Email(message = "请填写正确的邮箱地址")
private String email;
@NotEmpty(message = "级别不能为空")
private String level;
}
在实际开发中对于需要校验的字段都需要设置对应的业务提示,即message属性。
常见的约束注解如下:
注解 | 功能 |
---|---|
@AssertFalse | 可以为null,如果不为null的话必须为false |
@AssertTrue | 可以为null,如果不为null的话必须为true |
@DecimalMax | 设置不能超过最大值 |
@DecimalMin | 设置不能超过最小值 |
@Digits | 设置必须是数字且数字整数的位数和小数的位数必须在指定范围内 |
@Future | 日期必须在当前日期的未来 |
@Past | 日期必须在当前日期的过去 |
@Max | 最大不得超过此最大值 |
@Min | 最大不得小于此最小值 |
@NotNull | 不能为null,可以是空 |
@Null | 必须为null |
@Pattern | 必须满足指定的正则表达式 |
@Size | 集合、数组、map等的size()值必须在指定范围内 |
必须是email格式 | |
@Length | 长度必须在指定范围内 |
@NotBlank | 字符串不能为null,字符串trim()后也不能等于“” |
@NotEmpty | 不能为null,集合、数组、map等size()不能为0;字符串trim()后可以等于“” |
@Range | 值必须在指定范围内 |
@URL | 必须是一个URL |
第三步:定义校验类进行测试
1.RequestBody方式
示例:
对于json形式参数,需要在其前面加上@Validated 或 @Valid 注解
@PostMapping("test1")
public String test1(@Validated @RequestBody ValidVO validVO) {
return "success";
}
测试结果:
2.表单方式
示例:
@PostMapping("test2")
public String test2(@Validated ValidVO validVO) {
return "success";
}
测试结果:
TODO
3.单字段方式
示例:
单字段需要在Controller上加上 @Validated 注解,否则不生效
@PostMapping("test3")
public String test3(@Email String email) {
return "test3";
}
测试结果:
参数异常–全局异常处理器
首先继承 ResponseEntityExceptionHandler 并重写 handleMethodArgumentNotValid 方法
- fieldName:获取异常的字段
- defaultMessage:错误信息
此处可以自定义返回的逻辑,一般返回 defaultMessage 就够了
@Slf4j
@RestControllerAdvice
public class ValidateHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
StringJoiner errorMessage = new StringJoiner("; ");
for (var fieldError : ex.getBindingResult().getFieldErrors()) {
// 获取字段名和错误信息,并转换为小写
String fieldName = fieldError.getField().toLowerCase();
String defaultMessage = fieldError.getDefaultMessage().toLowerCase();
// 如果错误消息已经包含了字段名,则直接添加错误消息;否则拼接字段名和错误消息
errorMessage.add(defaultMessage.startsWith(fieldName) ? fieldError.getDefaultMessage() : fieldName + " " + defaultMessage);
break;
}
// 移除末尾多余的分号和空格
String finalErrorMessage = errorMessage.toString().trim();
// 返回统一的错误响应
Result<Void> result = new Result<>(HttpStatus.BAD_REQUEST.value(), finalErrorMessage, null);
return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
}
}
自定义参数校验
性别校验
1.创建自定义注解
注解部分
- @Retention(RUNTIME) 指定了该注解在运行时可见。
- @Repeatable(EnumString.List.class) 指定了该注解可重复使用。
- @Constraint(validatedBy = EnumStringValidator.class) 用于标明由哪个类执行校验逻辑。
参数部分
- String message():默认的消息数据,校验失败默认提示信息。
- Class<?>[] groups() 和 Class<? extends Payload>[] payload():用于框架分组和负载的设置。
- String[] value():设置允许的枚举值。
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Repeatable(SexValid.List.class)
@Constraint(validatedBy = SexValidator.class) // 标明由哪个类执行校验逻辑
@Documented
public @interface SexValid {
String message() default "value not in enum values";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] value();
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@interface List {
SexValid[] value();
}
}
2.校验验证器
一般需要实现 ConstraintValidator 接口,并重写 initialize 和 isValid 方法。
- initialize:用于验证器执行初始化时的操作,通过 constraintAnnotation 参数获取了注解中的枚举值,将其存储到 enumStrings 用于下一步的判断。
- isValid:
- 如果参数为空,则验证通过
- 判断器是否在列表中,存在则通过,否则验证失败!
@Component
@Slf4j
public class SexValidator implements ConstraintValidator<SexValid, String> {
private List<String> enumStrings;
@Override
public void initialize(SexValid constraintAnnotation) {
enumStrings = Arrays.asList(constraintAnnotation.value());
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return enumStrings.contains(value);
}
}
3.测试
将其加入到实体对象中并测试
@SexValid(value = {"男", "女"}, message = "性别必须为男/女")
private String sex;
测试结果:
ip地址校验
1.创建自定义注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IPAddressValidator.class)
@Documented
public @interface IPAddress {
String message() default "{ipaddress.invalid}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
2.校验验证器
public class IPAddressValidator implements ConstraintValidator<IPAddress, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
try {
InetAddress inetAddress = InetAddress.getByName(value);
return inetAddress instanceof Inet4Address;
} catch (UnknownHostException e) {
return false;
}
}
}
3.测试
@IPAddress(message = "ip地址错误")
private String ipAddress;
测试结果
分组校验
1.定义分组接口
public interface ValidGroup extends Default {
interface Crud extends ValidGroup {
interface Create extends Crud {
}
interface Update extends Crud {
}
interface Query extends Crud {
}
interface Delete extends Crud {
}
}
}
2.给参数分配分组
- 创建
- id必须为空
- 修改
- id不能为空
- sex不能为空且进行校验
@Data
public class ValidVO {
@Null(groups = ValidGroup.Crud.Create.class)
@NotNull(groups = ValidGroup.Crud.Update.class, message = "应用Id不能为空")
private String id;
@SexValid(groups = ValidGroup.Crud.Update.class, value = {"男", "女"}, message = "性别必须为男/女")
@NotNull(groups = ValidGroup.Crud.Update.class, message = "性别不能为空")
private String sex;
@IPAddress(message = "ip地址错误")
private String ipAddress;
}
3.验证
@PostMapping("add")
public String add(@Validated(value = ValidGroup.Crud.Create.class)
@RequestBody ValidVO validVO) {
return validVO.toString();
}
@PostMapping("update")
public String update(@Validated(value = ValidGroup.Crud.Update.class)
@RequestBody ValidVO validVO) {
return validVO.toString();
}