策略模式与元数据映射模式融合 JSR 380 验证规范实现枚举范围校验

类文件

java 复制代码
@Target({
        ElementType.METHOD,
        ElementType.FIELD,
        ElementType.ANNOTATION_TYPE,
        ElementType.CONSTRUCTOR,
        ElementType.PARAMETER,
        ElementType.TYPE_USE
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
        validatedBy = {InEnumValidator.class, InEnumCollectionValidator.class}
)
public @interface InEnum {

    /**
     * @return 实现 ArrayValuable 接口的类
     */
    Class<? extends ArrayValuable<?>> value();

    String message() default "必须在指定范围 {value}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}
java 复制代码
public class InEnumCollectionValidator implements ConstraintValidator<InEnum, Collection<?>> {

    private List<?> values;

    @Override
    public void initialize(InEnum annotation) {
        ArrayValuable<?>[] values = annotation.value().getEnumConstants();
        if (values.length == 0) {
            this.values = Collections.emptyList();
        } else {
            this.values = Arrays.asList(values[0].array());
        }
    }

    @Override
    public boolean isValid(Collection<?> list, ConstraintValidatorContext context) {
        if (list == null) {
            return true;
        }
        // 校验通过
        if (CollUtil.containsAll(values, list)) {
            return true;
        }
        // 校验不通过,自定义提示语句
        context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值
        context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()
                .replaceAll("\\{value}", CollUtil.join(list, ","))).addConstraintViolation(); // 重新添加错误提示语句
        return false;
    }
}
java 复制代码
public class InEnumValidator implements ConstraintValidator<InEnum, Object> {

    private List<?> values;

    @Override
    public void initialize(InEnum annotation) {
        ArrayValuable<?>[] values = annotation.value().getEnumConstants();
        if (values.length == 0) {
            this.values = Collections.emptyList();
        } else {
            this.values = Arrays.asList(values[0].array());
        }
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        // 为空时,默认不校验,即认为通过
        if (value == null) {
            return true;
        }
        // 校验通过
        if (values.contains(value)) {
            return true;
        }
        // 校验不通过,自定义提示语句
        context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值
        context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()
                .replaceAll("\\{value}", values.toString())).addConstraintViolation(); // 重新添加错误提示语句
        return false;
    }
}
java 复制代码
public interface ArrayValuable<T> {

    /**
     * @return 数组
     */
    T[] array();

} 
java 复制代码
@Getter
@AllArgsConstructor
public enum CommonStatusEnum implements ArrayValuable<Integer> {

    ENABLE(0, "开启"),
    DISABLE(1, "关闭");

    public static final Integer[] ARRAYS = Arrays.stream(values()).map(CommonStatusEnum::getStatus).toArray(Integer[]::new);

    /**
     * 状态值
     */
    private final Integer status;
    /**
     * 状态名
     */
    private final String name;

    @Override
    public Integer[] array() {
        return ARRAYS;
    }

    public static boolean isEnable(Integer status) {
        return ObjUtil.equal(ENABLE.status, status);
    }

    public static boolean isDisable(Integer status) {
        return ObjUtil.equal(DISABLE.status, status);
    }

}

1. 策略模式(Strategy Pattern)

核心体现

  • InEnumValidator 和 InEnumCollectionValidator 是两个不同的验证策略:
    • InEnumValidator:验证单个值是否在枚举允许范围内
    • InEnumCollectionValidator:验证集合中所有元素是否都在枚举允许范围内
  • 通过 @Constraint(validatedBy = {...}) 动态选择验证策略,符合策略模式的多态特性。

模式优势

  • 开闭原则:新增验证类型(如数组验证)只需添加新策略类,无需修改已有代码。
  • 解耦验证逻辑:将不同场景的校验规则分离到独立类中。

2. 元数据映射模式(Metadata Mapping Pattern)

核心体现

  • ArrayValuable 接口:通过 array() 方法将枚举值映射为统一元数据格式(数组)。
  • InEnum 注解:通过 value() 属性关联具体枚举类,建立校验规则与元数据的映射关系。

代码示例

java 复制代码
// 元数据定义:CommonStatusEnum 提供状态数组
public enum CommonStatusEnum implements ArrayValuable<Integer> {
    ENABLE(0, "开启"), DISABLE(1, "关闭");
    public static final Integer[] ARRAYS = Arrays.stream(values()).map(...);
    @Override public Integer[] array() { return ARRAYS; }
}

// 元数据使用:验证器通过 ArrayValuable 获取校验范围
public class InEnumValidator implements ConstraintValidator<InEnum, Object> {
    private List<?> values;
    @Override public void initialize(InEnum annotation) {
        ArrayValuable<?>[] values = annotation.value().getEnumConstants();
        this.values = Arrays.asList(values[0].array());
    }
}

模式优势

  • 统一元数据接口:所有实现 ArrayValuable 的枚举自动具备提供校验范围的能力。
  • 动态元数据获取:通过反射机制实现运行时元数据解析。

3. JSR 380 规范扩展

核心体现

  • 自定义注解 @InEnum:通过实现 ConstraintValidator 接口扩展标准校验规则。
  • 校验逻辑复用:InEnumValidator 和 InEnumCollectionValidator 复用相同的元数据映射逻辑。

典型应用场景

java 复制代码
// 在 DTO 中使用自定义校验注解
public class UserStatusDTO {
    @InEnum(CommonStatusEnum.class)
    private Integer status; // 自动校验 status 是否为 0 或 1
}

4. 设计优势总结

特性 实现方式
扩展性 新增枚举只需实现 ArrayValuable,无需修改验证逻辑
类型安全 通过泛型 ArrayValuable 保证元数据类型与校验值类型一致
校验语义明确 @InEnum(CommonStatusEnum.class) 直观表达业务约束
错误提示友好 通过 context.buildConstraintViolationWithTemplate 动态生成错误信息

5. 同类设计对比

传统硬编码校验方式:

java 复制代码
// 非模式化的校验逻辑(缺乏扩展性)
if (!(status == 0 || status == 1)) {
    throw new IllegalArgumentException("状态值无效");
}
  • 对比优势:当前设计通过策略+元数据映射,实现校验逻辑与业务代码解耦,符合现代化框架的声明式校验趋势。
相关推荐
沛沛老爹5 小时前
软件架构风格系列(2):面向对象架构
spring·软件架构风格·面向对象架构·架构入门
lyrhhhhhhhh5 小时前
Spring 框架 JDBC 模板技术详解
java·数据库·spring
亚林瓜子5 小时前
AWS Elastic Beanstalk控制台部署Spring极简工程
java·spring·云计算·aws·eb
亚林瓜子7 小时前
Spring集成Redis中禁用主机名DNS检测
redis·spring·ssh
西北大程序猿10 小时前
日志与策略模式
策略模式
CircleMouse12 小时前
基于 RedisTemplate 的分页缓存设计
java·开发语言·后端·spring·缓存
Mast Sail14 小时前
windows下authas调试tomcat
java·windows·tomcat·authas
努力学习的明14 小时前
Spring MVC 对 JavaWeb 的优化:从核心组件到注解
java·spring·mvc·web
疯狂的挖掘机14 小时前
记一次从windows连接远程Linux系统来控制设备采集数据方法
linux·运维·windows