zoukankan      html  css  js  c++  java
  • 利用Aspectj实现Oval的自动参数校验

    前言:
      Oval参数校验框架确实小巧而强大, 他通过注解的方式配置类属性, 然后通过Oval本身自带的工具类, 快速便捷执行参数校验. 但是工具类的校验需要额外的代码编写, 同时Oval对函数参数级的校验, 默认情况下并不生效.
      本文将讲述借助Aspectj, 并结合Oval, 来是参数的自动校验.

    举个例子:
      编写如下测试代码:

    @Getter
    @Setter
    public class CCBReq {
    
        @NotNull(message = "message字段不能为空")
        private String message;
    
    }
    
    // *) Oval校验工具类, 用于实体类的快速校验
    public class OvalCheckHelper {
    
        public static void validate(Object obj) {
            Validator validator = new Validator();
            List<ConstraintViolation> cvs = validator.validate(obj);
            if ( cvs != null && cvs.size() > 0 ) {
                throw new ConstraintsViolatedException(cvs);
            }
        }
    
    }

      具体的服务类, 以及常规的校验方式代码:

    @Component("ovalService")
    public class OvalService {
    
        // *) 函数参数没法直接校验
        public String echo1(@NotNull String msg) {
            return msg;
        }
    
        public String echo2(CCBReq req) {
            // *) 需要通过工具类, 进行快速校验
            OvalCheckHelper.validate(req);
            return req.getMessage();
        }
    
    }

      这边使用Oval框架进行校验, 就遇到两个常规的不完美的点.
      1. 函数参数没法直接校验(设置了Oval注解, 但无法利用)

    public String echo1(@NotNull String msg) { } 

      这边的@NotNull完全变成了为了可读性而添加的注解, Oval框架也没法直接利用到它.
      2. 实体类的校验, 需要额外的代码

    OvalCheckHelper.validate(req);

      这类代码会附带到各个具体的服务类的方法中, 一定程度上也是高度耦合进了业务代码中了.

    Aspectj简介:
      Aspectj是面向切面的编程, 其定义了切入点(PointCut)以及基于切入点的(@Before, @After, @Around)这些切面操作.
      具体可以参阅如下文章: AspectJ基本用法, 我这边也不展开了.
      就多讲点一些关于切点(PointCut)里的call/execution/@annotation的区别:
      使用call指令, 扩展后的代码类似如下:

    Call(Before)
    Pointcut{
        Pointcut Method
    }
    Call(After)

      而使用execution/@annotation指令, 扩展后的代码类似如下:

    Pointcut{
        execution(Before)
        Pointcut Method
        execution(After)
    }
    

      

    解决思路:
      让我们直接给一个解决方案吧.
      首先定义注解, 用于PointCut的切入点的确定.

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface OvalArgsCheck {
    }

      然后定义一个具体的切面:

    import net.sf.oval.ConstraintViolation;
    import net.sf.oval.Validator;
    import net.sf.oval.exception.ConstraintsViolatedException;
    import net.sf.oval.guard.Guard;
    import org.aopalliance.intercept.MethodInvocation;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.List;
    
    @Aspect
    @Component
    public class OvalArgsAdavice {
    
        @Pointcut("@annotation(com.test.oval.OvalArgsCheck)")
        public void checkArgs() {
        }
    
        @Before("checkArgs()")
        public void joint(JoinPoint joinPoint) throws Exception {
    
            MethodInvocationProceedingJoinPoint mjp =
                    ((MethodInvocationProceedingJoinPoint) joinPoint);
    
            // *) 获取methodInvocation对象
            MethodInvocation mi = null;
            try {
                Field field = MethodInvocationProceedingJoinPoint
                        .class.getDeclaredField("methodInvocation");
                field.setAccessible(true);
                mi = (MethodInvocation)field.get(mjp);
            } catch (Throwable e) {
            }
    
            if ( mi != null ) {
                // 获取Guard对象的validateMethodParameters方法
                Guard guard = new Guard();
                Method dm = Guard.class.getDeclaredMethod(
                        "validateMethodParameters",
                        Object.class,
                        Method.class,
                        Object[].class,
                        List.class
                );
                dm.setAccessible(true);
    
     	    // *) 对函数中标注Oval注解的参数, 直接进行校验, 用于解决第一类问题
                List<ConstraintViolation> violations = new ArrayList<ConstraintViolation>();
                dm.invoke(guard, mi.getThis(), mi.getMethod(), mi.getArguments(), violations);
                if ( violations.size() > 0 ) {
                    throw new ConstraintsViolatedException(violations);
                }
    
                // *) 以下是对函数中实体类(内部属性标记Oval注解)的参数, 进行校验, 用于解决第二类问题
                Validator validator = new Validator();
                for ( Object obj : mi.getArguments() ) {
                    if ( obj == null ) continue;
                    List<ConstraintViolation> cvs = validator.validate(obj);
                    if ( cvs != null && cvs.size() > 0 ) {
                        throw new ConstraintsViolatedException(cvs);
                    }
                }
    
            }
    
        }
    
    
    }

      然后在之前的具体服务上的方法上, 添加注解:

    @Component("ovalService")
    public class OvalService {
    
        @OvalArgsCheck
        public String echo1(@NotNull String msg) {
            return msg;
        }
    
        @OvalArgsCheck
        public String echo2(CCBReq req) {
            return req.getMessage();
        }
    
    }
    

      

    实战:
      为了激活Aspectj, 我们需要在spring的配置中, 需要如下配置.

    <!-- 激活aspectj功能 -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
    <!--  指定spring容器bean自动扫描的范围 -->
    <context:component-scan base-package="com.test.oval"/>

      然后编写测试代码:

    @ContextConfiguration("classpath:spring-test-oval.xml")
    @RunWith(SpringJUnit4ClassRunner.class)
    public class OvalTest {
    
        @Resource
        private OvalService ovalService;
    
        @Test(expected = ConstraintsViolatedException.class)
        public void testCase1() {
            ovalService.echo1(null);
        }
    
        @Test(expected = ConstraintsViolatedException.class)
        public void testCase2() {
            CCBReq ccbReq = new CCBReq();
            ovalService.echo2(ccbReq);
        }
    
    }

      测试结果皆为pass.
      由此采用该方案, 函数参数中, 无论是直接Oval注解修饰, 还是实体类中属性修饰, 都可以无缝地实现自动参数校验.

    后记:
      之前写过一篇文章: Dubbo的Filter实战--整合Oval校验框架. 本文算是对这篇文章的一些补充, 该方案是可以使用于dubbo接口中的参数自动校验中, 不过需要额外的Dubbo filter支持, 不过这不算太难.
      采用切面编程, 某种程度上, 大大减少了重复代码的编写, 简单易配置, 提高了编程效率.

  • 相关阅读:
    处理某客户p570硬盘故障所思
    Android手机使用WIFI及USB建立FTP服务器总结
    Metro界面的真正意义
    找工作之面试血泪史
    vim7.4官方源码在vs2013的编译方法及问题总结
    关于vs2012/2013的C编译器生成的exe的向后兼容xp的问题
    【转】一篇关于32位Linux内核使用大内存的文章——Hugemem Kernel Explained  &nb
    直接修改Android软件数据库来改变软件设置实例一则
    解决MyEclipse中安装或升级ADT之后SDK Target无法显示的问题
    国行Android手机使用google全套GMS服务小结
  • 原文地址:https://www.cnblogs.com/mumuxinfei/p/9328057.html
Copyright © 2011-2022 走看看