@Validated注解参数校验功能封装

2 篇文章 0 订阅
本文介绍了如何通过自定义校验注解和处理器,减少接口参数验证时的重复工作。作者实现了一个校验器代理,兼容多种校验场景,并提供了校验处理器接口,用于处理不同类型的注解。通过这种方式,开发者只需添加注解如@NotNull,无需手动设置message,系统会自动根据@ApiModelProperty的value拼接错误信息。
摘要由CSDN通过智能技术生成

版权说明: 本文由博主keep丶原创,转载请注明出处。
原文地址: https://blog.csdn.net/qq_38688267/article/details/115720412

场景介绍

  在定义接口参数对象时,我们需要通过@ApiModelProperty注解声明字段信息,还需要通过@NotNull/@NotEmpty等注解限制字段值信息,而这些校验字段都 需要填写message属性来自定义校验失败后的消息内容,这个工作量比较大,重复工作也很多。如图:
在这里插入图片描述
  因此,想要开发一个自定义的校验器或者自定义注解,实现根据@ApiModelProperty#value()获取字段名,然后根据校验类型拼接描述字符,比如 @NotNull注解拼接 “必填”、@NotEmpty拼接 "不能为空"等

  如果实现后则以后只需要加@NotNull/@NotEmpty注解,不需要再一个个写@NotNull#message()的值,可以减少很多重复的不必要的工作!
  
  

实现细节

校验器代理配置

  @Validated注解不仅能校验方法参数,还能校验返回值等多个功能。而我们暂时只需要重写其校验方法参数的功能,因此我们做的自定义校验还得兼容其他应用场景,因为有很多第三方jar也有用到@Validated进行校验。

  思来想去,尝试了几种方案后选择类似于多数据源的方案,通过一个代理类来实现多个校验器的路由,除了参数校验用自定义的校验器校验,其他的都使用默认的校验器校验。

  部分代码如下:

public class IfmValidatorProxy implements Validator, ExecutableValidator {

    private final Validator defaultValidator;

    private final Validator customValidator;

    public IfmValidatorProxy(Validator defaultValidator,
                             Validator customValidator) {
        this.defaultValidator = defaultValidator;
        this.customValidator = customValidator;
    }
}
   
    /**
     * 配置一个校验器的代理类,自定义选择校验器进行校验
     * <p>
     * 这里必须加上@Primary注解,否则会跟{@link WebMvcConfigurationSupport#mvcValidator()}的冲突
     */
    @Primary
    @Bean
    public Validator primaryValidator() {
        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
        MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
        factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
        IfmValidator ifmValidator = new IfmValidator();
        factoryBean.afterPropertiesSet();
        return new IfmValidatorProxy(factoryBean.getValidator(), ifmValidator);
    }

校验处理接口抽象

   保证扩展性,我们抽象一个接口:

/**
 * 校验注解处理抽象接口
 * <p>
 * ### 实现类要使用起来需要注册到{@link ValidateHandlerHelper}中 ###
 *
 * @author zzf
 * @date 2021/4/15 9:22
 */
public interface ValidateAnnotationHandler<T extends Annotation> {

    /**
     * 获取实现类的具体的注解类对象
     */
    Class<T> getAnnotation();

    /**
     * 判断参数字段是否存在该注解
     *
     * @param field 字段对象
     * @return 是否存在该注解
     */
    default Boolean isAnnotationPresent(Field field) {
        return field.isAnnotationPresent(getAnnotation());
    }

    /**
     * 校验字段值,如果值合法返回null,不合法返回消息内容
     *
     * @param field        需要校验的字段
     * @param targetObject 字段所在的对象
     * @return 值合法返回null,不合法返回消息内容
     */
    default String validate(Field field, Object targetObject) {
        T annotation = field.getAnnotation(getAnnotation());
        return validate(annotation, ReflectUtil.getFieldValue(targetObject, field));
    }


    /**
     * 校验字段值,如果值合法返回null,不合法返回消息内容
     * <p>
     * 这里之所以没有直接返回boolean而是直接返回msg
     * 是为了适配如{@link javax.validation.constraints.Max}{@link javax.validation.constraints.Min}等
     * 消息中需要获取注解属性值的注解
     *
     * @param validateAnnotation 标记的注解
     * @param fieldValue         字段值
     * @return 如果值合法返回null,不合法返回消息内容
     */
    String validate(T validateAnnotation, Object fieldValue);

    /**
     * @return 消息模板
     */
    String getResultMsgWhenInvalid();
}

校验处理器接口实现

  直接贴代码啦:

public class MaxHandler implements ValidateAnnotationHandler<Max> {

    @Override
    public Class<Max> getAnnotation() {
        return Max.class;
    }

    @Override
    public String validate(Max validateAnnotation, Object fieldValue) {
        long value = validateAnnotation.value();
        boolean valid;
        if (fieldValue instanceof Integer) {
            valid = value > (Integer) fieldValue;
        } else if (fieldValue instanceof BigDecimal | fieldValue instanceof Double | fieldValue instanceof Short) {
            valid = new BigDecimal(String.valueOf(value)).compareTo(new BigDecimal(String.valueOf(fieldValue))) > 0;
        } else if (fieldValue instanceof Long) {
            valid = value > (Long) fieldValue;
        } else if (fieldValue instanceof Date) {
            valid = value > ((Date) fieldValue).getTime();
        } else {
            valid = true;
        }
        if (!valid) {
            MessageFormatter.format(getResultMsgWhenInvalid(), value).getMessage();
        }
        return null;
    }

    @Override
    public String getResultMsgWhenInvalid() {
        return "的值必须小于{}!";
    }
}

public class NotNullHandler implements ValidateAnnotationHandler<NotNull> {

    @Override
    public Class<NotNull> getAnnotation() {
        return NotNull.class;
    }

    @Override
    public String validate(NotNull validateAnnotation, Object fieldValue) {
        if(ObjectUtil.isNull(fieldValue)) {
            return getResultMsgWhenInvalid();
        }
        return null;
    }

    @Override
    public String getResultMsgWhenInvalid() {
        return "必传!";
    }
}

  

参数校验类工厂

  为了方便快速扩展和使用,包装了一个类似于简单工厂的对象:


public class ValidateHandlerHelper {

    private static final Set<ValidateAnnotationHandler<?>> handlerSet = new HashSet<>(10);

    /**
     * 初始化注册校验处理类
     */
    static {
        handlerSet.add(new NotNullHandler());
        handlerSet.add(new NotEmptyHandler());
        handlerSet.add(new NotBlankHandler());
        handlerSet.add(new MaxHandler());
        handlerSet.add(new MinHandler());
    }


    /**
     * 校验字段值,如果值合法返回null,不合法返回消息内容
     *
     * @param field        需要校验的字段
     * @param targetObject 字段所在的对象
     * @return 值合法返回null,不合法返回消息内容
     */
    private static String validate(Field field, Object targetObject) {
        Optional<ValidateAnnotationHandler<?>> handlerOptional =
                handlerSet.stream().filter(s -> s.isAnnotationPresent(field)).findFirst();
        if (handlerOptional.isPresent()) {
            String validate = handlerOptional.get().validate(field, targetObject);
            if (validate != null) {
                String fieldComment;
                if (field.isAnnotationPresent(ApiModelProperty.class)) {
                    ApiModelProperty annotation = field.getAnnotation(ApiModelProperty.class);
                    fieldComment = annotation.value();
                } else {
                    fieldComment = field.getName();
                }
                return fieldComment + handlerOptional.get().getResultMsgWhenInvalid();
            }
        }
        return null;
    }
全局异常捕获

  校验不通过的话,会抛出ConstraintViolationException异常,我们捕获后返回给前端:


    /**
     * 参数校验异常
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(ConstraintViolationException.class)
    public ServiceResponse<?> handlerConstraintViolationException(ConstraintViolationException e) {
        return new ServiceResponse<>(ResponseCodeEnum.PARAM_ERROR.getCode(), e.getMessage());
    }

  
  

使用

添加校验标识注解

在这里插入图片描述
  

添加校验注解

  在实现参数值校验时,我们做针对集合类型的字段做了递归处理,还可以深层校验集合元素属性哦~在这里插入图片描述
  

效果展示

在这里插入图片描述

源码地址

总结

  该功能只是初版,肯定还有很多不足和考虑不周的地方,还请各位大佬多多指点。后续还会继续优化迭代。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值