Spring AOP 通用入参校验终极版

入参校验一直是程序中一块鸡肋,食之无味却又不得不吃。经过几个版本变更,本次项目上线笔者终于将入参校验应用了稍微高级一点的写法。

基调:hibernate.validator
实现-低配版

1、引入pom

    <dependency>
     
        <groupId>org.hibernate</groupId>
     
        <artifactId>hibernate-validator</artifactId>
     
        <version>5.0.2.Final</version>
     
    </dependency>

2、封装校验类

    public class ValidationUtils {
        private static ILog logger = LogFactory.getLogger(ValidationUtils.class);
     
        /**
         * 使用hibernate的注解来进行验证
         */
        private static Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(true).buildValidatorFactory().getValidator();
     
        public static <T> void validate(T obj) {
            Set<ConstraintViolation<T>> constraintViolations = validator.validate(obj);
            // 抛出检验异常
            if (constraintViolations.size() > 0) {
                logger.warn("param invalidate fail :{}", constraintViolations.iterator().next().getMessage());
                System.out.println(constraintViolations.iterator().next().getMessage());
            }
        }
     
    }

3、应用

如对TradeDto进行校验则在TradeDto中必传的参数上增加@NotNull注解,并定义返回msg

    public class TradeDto {
        @NotNull(message = "订单ID为空")
        private Long orderId;
     
        @NotNull(message = "业务线ID为空")
        private Long businessId;
    .......
    }

方法处由原挨个判断改为调用封装类校验。

        public Result<TradeDto> createTrade(TradeDto tradeDto) {
            if (tradeDto == null || tradeDto.getOrderId() == null || tradeDto.getBusinessId() == null) {
                return ResultWrapper.fail(ErrorCode.PARAMETER_CANNOT_NULL);
            }
    替换为:
     
        ValidationUtils.validate(tradeDto);

4、结果

入参tradeDto中orderId为空时,返回“订单ID为空”的msg

******************************************************* 方案一完毕***********************************************************************

如果就这么结束了 未免也太low了。

方案一缺点:

1、每次入参校验都要写一段:ValidationUtils.....(简单但不太高级)

2、ValidationUtils封装类只校验单个dto,那如果入参有Long+Dto等组合情况呢?难不成调两遍ValidationUtils?(粗暴又低级)

3、validate方法返回个void,那调用处是接着执行呢,还是return呢?(返回值应结合业务逻辑)
实现-升级版

基调:AOP+注解;方法可变参数;返回值封装

1、注解类封装

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Valid {
    }

2、切面类封装

    @Aspect
    @Component
    @Order(2)
    public class ValidAspect {
        /**
         * 获取参数进行入参校验
         *
         * @param joinPoint
         * @return
         * @throws Throwable
         */
        @Around("execution(* com.trade.component..*.*(..)) && @annotation(com.trade.aspect.Valid))")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            Object[] args = joinPoint.getArgs();
            Result validate = ValidationUtils.validate(args);
            if (validate.isSuccess()) {
                return joinPoint.proceed();
            }
            return validate;
        }
    }

注:注解order(1)后面解释

3、校验方法优化(可变参数+业务有意义返回值)

        public static <T> Result validate(T... obj) {
            for (int i = 0; i < obj.length; i++) {
                if (obj[i] == null) {
                    return ResultWrapper.fail(ErrorCode.PARAMETER_IS_NULL);
                }
                Set<ConstraintViolation<T>> constraintViolations = validator.validate(obj[i]);
                if (constraintViolations.size() <= 0) {
                    continue;
                } else {
                    logger.warn("param invalidate fail :{}", constraintViolations.iterator().next().getMessage());
                    System.out.println(constraintViolations.iterator().next().getMessage());
                    return ResultWrapper.fail(constraintViolations.iterator().next().getMessage());
                }
            }
            return ResultWrapper.success();
        }

4、应用

        @Valid
        @Log
        @Override
        public Result<TradeDto> createTrade(TradeDto tradeDto) {..}

5、结果

添加@Valid注解执行方法入参校验,参数类型支持多个(基础类型+Dto组合传参)。

AOP执行顺序控制

之前博客写过使用AOP进行log入参出参统一日志输出的实现,如上createTrade方法,已有两个zidi自定义AOP作用于gaif该方法,那如何控制AOP执行顺序呢?@Order(int)注解就是这个作用。int值越小该切点yuex越先执行。所以Log切点xian项目应用@Order(1),先进行入参打印,而后执行参数校验@Valid。具体其他控制方法总结如下:

1、@Order(最直观简单)

2、配置文件(避免有些面试官刨根问底,知道即可)

    <aop:config expose-proxy="true">  
        <aop:aspect ref="aopBean" order="0">    
            <aop:pointcut id="testPointcut" expression="@annotation(xxx.xxx.xxx.annotation.xxx)"/>    
            <aop:around pointcut-ref="testPointcut" method="doAround" />    
            </aop:aspect>    
    </aop:config>  

3、显示实现Order接口

    @Component
    @Aspect
    public class MessageQueueAopAspect1 implements Ordered{
        @Override
        public int getOrder() {
            return 2;
        }
        
    }

原文:https://blog.csdn.net/Daybreak1209/article/details/82747672
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章