参考@NotBlank实现自定义注解校验器

✨这里是第七人格的博客。小七,欢迎您的到来~✨

🍅系列专栏:【工作小札】🍅

✈️本篇内容: 自定义注解校验器✈️

🍱本篇收录完整代码地址:见文章内容🍱

1 楔子

最近小七在做接口对接的时候,发现某些接口的同一个字段,可能需要做相同的校验。比如,在用户的注册和更新接口都需要判断用户的证件类型是否合法,许多小伙伴们的做法就是直接在代码里写if-else,这种在屎山拉屎的行为,小七实在是难以接收,想着看看能不能在上面雕雕花。

2 偷懒思路

2.1 思路一:工具类

多次复用的代码,我们很自然的想到抽象提取它。于是我们可以提供一个工具类,里面包含证件类型是否合法的校验,需要校验的时候,调用这个方法就好了。

好处:灵活,解耦

缺点:只解决的证件类型这一个参数的校验,如果多了其他类型,还得现实新的方法,写新的if-else。

2.2 思路二:切面

因为是校验入参,我们想一想能不能把这个校验放在Controller呢?把需要校验的接口,都放在切面里,在切面里写逻辑。

好处:入参校验逻辑前移,后面的代码可以更加专注业务逻辑

缺点:不太灵活,如果校验复杂,切面的逻辑将会很重

2.3 思路三:注解

结合上面两种思路,再进一步思考。如果我们可以像使用***@NotBlank***一样,也实现一个注解,这个注解只要传入我们的工具类,就能进行参数的校验,并且错误信息还可以定制化,那就完美了~

查看javax.validation的源码,我们可以发现一个神奇的接口,正好可以实现我们的这个需求,接口如下:

ConstraintValidator

3 代码实现

3.1 编写注解

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


/**
 * <p>字典校验注解</p>
 *
 * @author 第七人格
 * @date 2022/12/09
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DictionariesValidator.class)
public @interface DictionariesRef {

    /**
     * 引用的字典对象,该对象必须包含isValid的静态方法。
     *
     * @return 校验目标类。
     */
    Class<?> constDictClass();

    /**
     * 错误消息提示。
     *
     * @return 错误提示。
     */
    String message() default "无效的字典引用值!";

    /**
     * 验证分组。
     *
     * @return 验证分组。
     */
    Class<?>[] groups() default {};

    /**
     * 载荷对象类型。
     *
     * @return 载荷对象。
     */
    Class<? extends Payload>[] payload() default {};

}

3.2 编写实现类

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import *.DictionariesRef;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.Method;


/**
 * <p>字典验证器-实现类</p>
 *
 * @author 第七人格
 * @date 2022/12/09
 */
public class DictionariesValidator implements ConstraintValidator<DictionariesRef, Object> {

    /**
     * 注解
     */
    private DictionariesRef dictionariesRef;

    /**
     * 重写初始化方法
     *
     * @param dictionariesRef 字典校验器注解
     */
    @Override
    public void initialize(DictionariesRef dictionariesRef) {
        this.dictionariesRef = dictionariesRef;
    }

    /**
     * 重写校验方法
     *
     * true 表示校验通过
     * false 表示校验不通过
     *
     * @param object                          待校验对象
     * @param constraintValidatorContext 字典验证器上下文
     * @return boolean
     */
    @Override
    public boolean isValid(Object object, ConstraintValidatorContext constraintValidatorContext) {
        // 校验对象为空,则默认校验通过
        if (ObjectUtil.isEmpty(object)) {
            return true;
        }
        // 通过反射获取方法。划重点,这里也是为什么,待校验对象需要实现isValid方法的原因
        Method method =
                ReflectUtil.getMethodByName(dictionariesRef.constDictClass(), "isValid");
        return ReflectUtil.invokeStatic(method, object);
    }
}

4 实战展示

证件类型枚举(待校验类):

/**
 * <p>证件类型枚举</p>
 *
 * @author 第七人格
 * @date 2022/12/09
 */
@Getter
public enum CardTypeEnum {
    ID_CARD("I000001","身份证"),
    ;
    private String code;
    private String desc;

    CardTypeEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    /**
     * 得到枚举
     *
     * @param code 代码
     * @return {@link CardTypeEnum}
     */
    public static CardTypeEnum getEnum(String code) {
        for (CardTypeEnum ge : values()) {
            if(ge.getCode().equals(code)) {return ge;}
        }
        return null;
    }

    /**
     * 得到详情描述
     *
     * @param code 代码
     * @return {@link String}
     */
    public static String getDesc(String code) {
        for (CardTypeEnum ge : values()) {
            if(ge.getCode().equals(code)) {return ge.getDesc();}
        }
        return null;
    }

    /**
     * 是有效
     *
     * true 表示有效
     * false 表示无效
     *
     * @param code 代码
     * @return boolean
     */
    public static boolean isValid(String code) {
        return getEnum(code) != null;
    }
}

入参:

/**
 * <p>入参</p>
 *
 * @author 第七人格
 * @date 2022/12/09
 */
@Data
public class DemoReq implements Serializable {

    /**
     * 证件类型
     * 
     * 注解DictionariesRef 
     * 第一个参数 constDictClass 传入我们的校验枚举类,此枚举实现了isValid方法
     * 第二个参数 message 传入我们自定义的错误信息
     */
    @NotBlank(message = "证件类型不能为空")
    @DictionariesRef(constDictClass = CardTypeEnum.class, message = "证件类型非法!")
    private String cardType;
}

Controller:

/**
 * <p>控制层demo</p>
 *
 * @author 第七人格
 * @date 2022/12/09
 */
@RestController
@RequestMapping("/demo")
public class DemoController {
    
    @PostMapping(value = "/v1/apply")
    public void apply(@Valid @RequestBody DemoReq req){
		// todo
    }

}

测试结果:

当cardType传入值为I000001时,检验通过,未见异常。

当cardType传入值不为I000001时,检验不通过,抛出异常信息:证件类型非法!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

第七人格

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值