「这是我参与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
包下大家都可以去使用的。