你还在用 if 判断参数是否在枚举范围内吗?我已经用上注解了。

1,240 阅读4分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。

通过自定义注解来实现参数值的枚举范围校验,适用于场景就是当一个 int, String 值是在一个枚举的范围内,通过 Spring MVC 中的参数校验拓展来实现规范的参数校验,保证用户传入参数的有效性。以减少代码中的 if ... else 逻辑。

自定义注解

依赖导入

在我们的 pom.xml 文件中引入 Spring MVC 的依赖,为了方便我们就使用 Spring Boot 的 Starter 即可。

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <version>2.0.6.RELEASE</version>
</dependency>

注解定义

编写自定义注解,我解释一下下面的几个参数:
1、mssage() 默认错误消息, 和标准的 @NotNull等一样,主要是提示一个 message.
2、value() 这里是一个枚举范围,就是参数字段限定的枚举范围。但是需要注意的是,咱们的枚举定义的过程中只要有 value 字段的属性和 getValue 方法。

/**
 * 枚举值限定校验
 * 注意:枚举一定要包含 value 字段
 *
 * @author zhengsh
 * @date 2021-12-08
 */
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {EnumValueValidator.class})
public @interface EnumValue {
    
    // 默认错误消息
    String message() default "the integer is not one of the enum values";

    // 约束注解在验证时所属的组别
    Class<?>[] groups() default {};

    // 约束注解的有效负载
    Class<? extends Payload>[] payload() default {};

    Class<? extends Enum> value();

    // 同时指定多个时使用
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @interface List {
        EnumValue[] value();
    }
}

定义校验器

编写自定义校验器, 在 EnumValueValidator自定义校验器中,咱们需要实现 ConstraintValidator 其实说白了在参数封装的过程中,Spring MVC 框架会去调用 initialize方法对当前这个注解的拓展进行初始化,然后在校验过程中会执行 isValid 如果校验通过我们就返回 true , 不通过我们就返回 false 。如果不通过,就会抛出异常,然后异常的错误信息中会包含我们之前定义的 message.

/**
 * 与约束注解关联的校验器
 *
 * @author zhengsh
 * @date 2021-12-08
 */
public class EnumValueValidator implements ConstraintValidator<EnumValue, Object> {

    private Class<? extends Enum> enumClass;
    private static final String METHOD_NAME = "getValue";

    /**
     * 这个方法做一些初始化校验
     *
     * @param constraintAnnotation
     */
    public void initialize(EnumValue constraintAnnotation) {
        enumClass = constraintAnnotation.value();
        try {
            // 先判断该enum是否实现了getValue方法
            enumClass.getDeclaredMethod(METHOD_NAME);
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("the enum class has not getValue method", e);
        }
    }

    /**
     * 这个方法写具体的校验逻辑:校验数字是否属于指定枚举类型的范围
     *
     * @param value
     * @param constraintValidatorContext
     * @return
     */
    public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
        // 如果为空返回 true , 判空用 @NotNull 等专用注解
        if (Objects.isNull(value)) {
            return true;
        }
        try {
            Enum[] enumConstants = enumClass.getEnumConstants();
            if (enumConstants == null) {
                // 如果不是枚举类型,返回 enumConstants = null
                return true;
            }
            for (Enum e : enumClass.getEnumConstants()) {
                Method declaredMethod = e.getClass().getDeclaredMethod(METHOD_NAME);
                Object obj = declaredMethod.invoke(e);
                if (Objects.equals(obj, value)) {
                    return true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}

使用示例

下面是我们使用的例子我们需要做三步:
1、定义限定范围的注解.
2、在入参接受的对象上增加 @EnumValue 注解 .
3、在控制器的请求参数方法上增加 Validated 标记需要进行参数验证处理。
下面来看看具体的代码和实现。

定义枚举

首先我们先定义一个 Gender 枚举

public enum Gender {
    male(0),
    female(1);

    private int value;

    Gender(int value) {
        this.value = value;
    }

    public int getValue() {
        return this.value;
    }
}

使用和依赖

然后我们在我们的入参上面添加自定义注解即可:

public class Person {
  @EnumValue(value = Gender.class, message = "gender 字段不在系统支持的枚举范围内")
  Gender gender;
}

最后我们需要在的Controler 的方法上面加上 @Validated注解,表示对当前参数进行校验。

@RestController
public class PersonController {

    @RequestMapping("/test")
    public String test(@Validated Person person) {
        return "OK";
    }

}

使用总结

在 Java 编程的中,通过注解是一种非常简化代码的方法,就如我们 @EnumValue(Gender.class)注解的使用,我们减少了对参数的判断,而且可以在 EnumValueValidator中统一的做处理,方便以后的拓展,而且可以规范化的做一些开发的基础工作,让我们的业务开发者更加的聚焦业务开发。

validation-api中也是提供了非常多的参数校验枚举比如:@NotNull@Min @Email 等注解在 jakarta.validation.constraints 包下大家都可以去使用的。

参考资料