利用自定义Validator和枚举类来限定接口的入参
趁热记录下,给未来的自己
0. 前言
Spring Validation
作为一个参数验证框架,本身提供的注解已经很强大了,能覆盖大部分业务场景需求,比如:@NotNull
, @NotBlank
, @Min
, @Max
, @Size
, @Email
等等。
但是对于更加复杂的业务场景,Spring Validation
自带的这些注解就无能为力了,比如,假设有个接口,其中的一个入参是 String
类型,由于业务需要,该入参值的范围只能在一个允许的 list
中,不在范围中的传入值,直接返回异常。
常规做法是在业务代码里对入参做一层逻辑判断,但是这样做会导致业务代码和校验代码耦合,且开发效率不高,代码十分不美观。
更加优雅的做法是自定义一个 validator
配合枚举类,来实现这个需求。
1. 架构图
模块说明:
- 1:
DTO
- 接收入参的DTO
类,使得收到的参数变成一个对象- 1-1:
private String para
对象加了@EnumCheck
注解,表示para
需要被校验 - 1-2:
private String para1
对象未加@EnumCheck
注解,表示para1
不需要被校验
- 1-1:
- 2:
EnumCheck
- 是一个注解类,需要在该类里实现自定义注解 - 3:
EnumUtil
- 枚举工具类,通过传入一个枚举参数,判断该参数是否在指定的枚举里,存在则返回枚举,不存在返回null
- 4:
Enum Class
- 用户定义的枚举类,用于存放入参的范围 - 5: 异常逻辑 - 当
EnumCheck
失败后,进入异常逻辑 - 6: 后续业务逻辑 - 当
EnumCheck
成功后,进入后续的业务逻辑
2. 代码说明
2.1 JenkinsProcessBuildReqDTO.java
入参用该DTO
类接收,在该类中定义需要被自定义validate
的一个或者多个字段。
@Data
public class JenkinsProcessBuildReqDTO {
/**
* 部署环境
*/
@EnumCheck(message = "environment输入有误", enumClass = JenkinsProcessEnum.class)
private String environment;
}
2.2 JenkinsProcessEnum.java
用户自定义的枚举类,用于存放入参的范围,如该枚举类中,限定了 JenkinsProcessBuildReqDTO.environment
的范围在 DEPLOY_SIT
, DEPLOY_UAT
, DEPLOY_SIT_UAT
这三个枚举里。
import lombok.Getter;
@Getter
public enum JenkinsProcessEnum {
/**
* sit 环境
*/
DEPLOY_SIT("0", "SIT", "sit 环境"),
/**
* uat 环境
*/
DEPLOY_UAT("1", "UAT", "uat 环境"),
/**
* sit 和 uat 环境
*/
DEPLOY_SIT_UAT("2", "SIT_UAT", "sit 和 uat 环境"),
;
private String code;
private String name;
private String desc;
JenkinsProcessEnum(String code, String name, String desc) {
this.code = code;
this.name = name;
this.desc = desc;
}
}
2.3 EnumUtil.java
该类中提供了一个方法getEnumByParameter
: 通过传入一个枚举参数 enumParameter
,判断该参数是否在指定的枚举 clazz
里。存在则返回 enumParameter
对应的枚举,不存在返回 null
@Slf4j
public class EnumUtil {
/**
*
* @author arkMon
* @date 14:22 2021/2/23
* @param clazz 传入的枚举类名
* @param getEnumMethodName 传入的枚举类clazz中的方法名称
* @param enumParameter 枚举参数
* @return T 具体的枚举值 或者 null
*/
public static <T extends Enum<T>> T getEnumByParameter(Class<T> clazz, String getEnumMethodName, Object enumParameter){
T result = null;
try{
//Enum接口中没有values()方法,我们仍然可以通过Class对象取得所有的enum实例
T[] arr = clazz.getEnumConstants();
//获取定义的方法
Method targetMethod = clazz.getDeclaredMethod(getEnumMethodName);
if (targetMethod == null) {
log.error("getEnumMethodName=" + getEnumMethodName + " 不存在");
return null;
}
Object typeVal;
//遍历枚举定义
for(T entity:arr){
if (enumParameter instanceof Integer) {
typeVal = Integer.valueOf(String.valueOf(targetMethod.invoke(entity)));
} else if (enumParameter instanceof String) {
//获取传过来方法的
typeVal = String.valueOf(targetMethod.invoke(entity)).replace(" ","");
//执行的方法的值等于参数传过来要判断的值
enumParameter = ((String) enumParameter).replace(" ", "");
} else {
log.error("传入的enumType不是Integer也不是String类型");
return null;
}
if(typeVal.equals(enumParameter)){
//返回这个枚举
result = entity;
break;
}
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
return result;
}
}
2.4 EnumCheck.java
自定义验证器的注解。在该注解下的validator
类中的isValid
方法里,实现自定义验证器的逻辑:
判断传入的参数是否可以通过枚举里的code
或者name
,用getEnumByParameter
方法找到其对应的枚举,能找到返回true
,不能找到返回false
。
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumCheck.Validator.class)
public @interface EnumCheck {
String message() default "{enum.value.invalid}"; // 错误信息
Class<? extends Enum<?>> enumClass(); // 枚举类
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String enumMethodCode() default "getCode"; // 枚举校验方法
String enumMethodName() default "getName"; // 枚举校验方法
boolean allowNull() default false; // 是否允许为空
class Validator implements ConstraintValidator<EnumCheck, Object> {
private Class<? extends Enum<?>> enumClass;
private String enumMethodCode;
private String enumMethodName;
private boolean allowNull;
@Override
public void initialize(EnumCheck enumValue) {
enumMethodCode = enumValue.enumMethodCode();
enumMethodName = enumValue.enumMethodName();
enumClass = enumValue.enumClass();
allowNull = enumValue.allowNull();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
if (value == null) {
return allowNull;
}
if (enumClass == null) {
return Boolean.TRUE;
}
JenkinsProcessEnum enumByParameter = EnumUtil.getEnumByParameter(JenkinsProcessEnum.class, enumMethodCode, value);
if (enumByParameter != null) {
return true;
}
JenkinsProcessEnum enumByParameter1 = EnumUtil.getEnumByParameter(JenkinsProcessEnum.class, enumMethodName, value);
if (enumByParameter1 != null) {
return true;
}
return false;
}
}
}
Ref
转载自:https://juejin.cn/post/6932376477547790349