相关阅读:
JAVA基础篇(4)-Validation验证框架
需求
一般我们会使用Validation注解对实体类的参数进行校验。在项目开发中,我们时常会遇到这种需求:
校验某个参数是否是系统特定的值(系统一般使用枚举类来充当数据字典)。
举个例子:某系统有请求参数有支付类型,而该系统的接口只提供01类型的快捷支付,02类型的余额支付。但用户上送了一个03类型。那么针对这种情况一般我们使用@Pattern(value)
正则表达式注解,但是这是一种将数据硬编码到注解中,并不是很推荐。或者在业务代码中通过if判断,但是会使得代码很冗杂。
有没有一种特定的注解可以去校验上送的参数是否是某个枚举类的枚举值呢?
解决方案
思路:开发人员在注解中指定枚举对象的class类型,枚举对象中的属性字段名。
校验器会通过内省(反射)获取到该枚举对象属性字段的值的集合。并进行缓存。判断请求参数是否在在该集合中。
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({METHOD, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = ValidateParamOfEnumValidator.class)
@Documented
public @interface ValidateParamOfEnum {
String message() default "请求参数错误!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* 枚举对象
*/
Class<? extends Enum> value();
/**
* 枚举类的属性名:默认的属性名是Type类型
*/
String fieldName() default "type";
}
@Slf4j
public class ValidateParamOfEnumValidator implements ConstraintValidator<ValidateParamOfEnum, Object> {
private static Map<String, Set<Object>> enumCache = new ConcurrentHashMap<>(32);
private Class clazz;
private String fieldName;
@Override
public void initialize(ValidateParamOfEnum constraintAnnotation) {
clazz = constraintAnnotation.value();
fieldName = constraintAnnotation.fieldName();
}
/**
* 请求参数一定是枚举类的参数
*/
@SneakyThrows
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
//判断该枚举类是否包含该参数
return value!=null&&getEnumValues().contains(value);
}
/**
* 根据枚举的属性名获取该枚举类到所有的枚举值。
*
* @return 该枚举类的所有枚举值
*/
private Set<Object> getEnumValues() {
//优化点2:缓存枚举类的返回值
String key = clazz.getName() + "_" + fieldName;
//该方法线程安全,存在就返回,不存在去创建并返回。
return enumCache.computeIfAbsent(key, k -> {
log.info("[加载[{}]的所有枚举类型]", key);
Object[] enumConstants = clazz.getEnumConstants();
Set<Object> enumValues = new HashSet<>();
//优化点1:为了避免反射性能损耗,使用Spring工具类,缓存反射对象
PropertyDescriptor type = BeanUtils.getPropertyDescriptor(clazz, fieldName);
if (type != null) {
Method method = type.getReadMethod();
for (Object obj : enumConstants) {
try {
enumValues.add(method.invoke(obj));
} catch (Exception e) {
log.error("校验参数异常", e);
return enumValues;
}
}
}
return enumValues;
});
}
}