业务场景:在接口开发中,我们有时候的入参是对应项目中的枚举,这就要求别人在调用接口时传入的参数值一定是要对应项目中的枚举值。但是在spring-boot-validation提供的校验中并没有枚举值的校验注解,虽然我们也可以通过断言Assert的方式来校验参数值,但是这样不够优雅,而且如果一个实体对象在不同的接口作为入参,那么需要在每个接口都要做参数校验,这就很笨重麻烦。所以就编写一个注解,作用于字段上。
一、自定义校验注解
/**
* 枚举参数校验注解
*/
@Target({
ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {
EnumIntegerValidator.class})
@Documented
public @interface ValidateEnum {
String message() default "invalid parameter";
String keyMethod() default "getKey";// 默认属性名称为key,可自定义
Class<?>[] groups() default {
};
Class<? extends Payload>[] payload() default {
};
/**
* 允许的枚举
* @return
*/
Class<? extends Enum<?>> enumClass();
/**
* 允许的枚举值, 约定为int类型, 判断优先级高于enumClass
* @return
*/
int[] enumValues() default {
};
}
@Constraint(validatedBy = {EnumIntegerValidator.class}) : 实现逻辑校验的类,
message、groups()、payload()是 注解类型变量的三个参数,必须定义的,因为使用了@Constraint
keyMethod:获取枚举值的方法,可以自定义
enumClass:校验的枚举
enumValuess():允许的枚举值, 约定为int类型, 判断优先级高于enumClass
二、自定义校验类实现ConstraintValidator接口
public class EnumIntegerValidator implements ConstraintValidator<ValidateEnum, Integer> {
private Class<? extends Enum> enumClass;
private int[] enumValues;
private String keyMethod;
@Override
public void initialize(ValidateEnum validateEnum) {
enumClass = validateEnum.enumClass();
enumValues = validateEnum.enumValues();
keyMethod = validateEnum.keyMethod();
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
if (value == null) {
return false;
}
try {
if(enumValues.length ==0 ){
//enumValues 优先级高
Enum[] enumArr = enumClass.getEnumConstants();
if(enumArr.length>0){
for (Enum e : enumArr) {
Method method = e.getDeclaringClass().getMethod(keyMethod);
Object keyValue = method.invoke(e);
if(keyValue != null){
if(value.equals(keyValue)){
return true;
}
}
}
return false;
}
}else {
for (int enumValue : enumValues) {
if(enumValue == value){
return true;
}
}
return false;
}
return true;
}catch (Exception e){
return false;
}
}
}
ConstraintValidator的泛型参数为:自定义的注解,和需要校验值的类型
initialize: 初始化方法,把注解的值注入
isValid:实现业务校验的方法,返回false就是校验不通过,会输出message的值,返回true就是校验通过。
优先校验enumValue的参数,如果限制值,就不会管枚举值。其次会通过反射,通过keyMethod的值拿到枚举的属性值然后做校验比较。
三、在参数上加上注解
枚举类定义
public enum ExpenseApprovalStatusEnum {
EXPENSE_UNSUBMIT(0, "待提交"),
EXPENSE_UNAPPROVAL(1, "待审核"),
EXPENSE_ADOPTED(2, "审核通过"),
EXPENSE_REJECT(3, "驳回");
private Integer key;
private String value;
ExpenseApprovalStatusEnum(Integer key, String value) {
this.key = key;
this.value = value;
}
public static String getValue(Integer key) {
for (ExpenseApprovalStatusEnum c : ExpenseApprovalStatusEnum.values()) {
if (c.getKey().equals(key)) {
return c.getValue();
}
}
return null;
}
public static Integer getKey(String value) {
for (ExpenseApprovalStatusEnum c : ExpenseApprovalStatusEnum.values()) {
if (c.getValue().equals(value)) {
return c.getKey();
}
}
return null;
}
public static Map<String, String> getOption() {
Map<String, String> option = new LinkedHashMap<>();
for (ExpenseApprovalStatusEnum c : ExpenseApprovalStatusEnum.values()) {
option.put(c.getKey().toString(), c.getValue());
}
return option;
}
public static ExpenseApprovalStatusEnum getEnum(Integer key) {
for (ExpenseApprovalStatusEnum c : ExpenseApprovalStatusEnum.values()) {
if (c.getKey().equals(key)) {
return c;
}
}
return null;
}
public Integer getKey() {
return key;
}
public void setKey(Integer key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
@ApiModelProperty(value = "审核状态(0待提交,1审核中,2审核通过,3驳回)")
@ValidateEnum(enumClass = ExpenseApprovalStatusEnum.class,enumValues = {
1,2},
message = "枚举值不正确",keyMethod = "getKey")
private Integer approvalStatus;
着重说下keyMethod的值,因为ExpenseApprovalStatusEnum枚举的getKey()是获取属性值的方法的,如果你要校验的枚举获取属性值的方法是getCode(),那么keyMethod应该传getCode,因为定义默认值是getKey,我这里可以不传这个参数的,只是为了演示使用效果。
四、开启注解校验(很重要)
一定一定要在参数上加上@Valid,因为@ValidateEnum的校验是实现ConstraintValidator接口的,如果不加上@Valid就不会进行参数校验。
public void test(@Valid ExpenseQueryBo expenseQueryBo){
....
}
public JsonResult<?> approval(@RequestBody @Valid ExpenseQueryBo expenseQueryBo) {
....
}