• Spring AOP的实现原理


    Spring AOP的实现原理是基于动态织入的动态代理技术,而AspectJ则是静态织入,而动态代理技术又分为Java JDK动态代理和CGLIB动态代理,前者是基于反射技术的实现,后者是基于继承的机制实现,下面通过一个简单的例子来分析这两种技术的代码实现。

    JDK动态代理

    先看一个简单的例子,声明一个A类并实现ExInterface接口,利用JDK动态代理技术在execute()执行前后织入权限验证和日志记录,注意这里仅是演示代码并不代表实际应用。

    /**
     * Created by zejian on 2017/2/11.*/
    //自定义的接口类,JDK动态代理的实现必须有对应的接口类
    public interface ExInterface {
        void execute();
    }
    
    //A类,实现了ExInterface接口类
    public class A implements ExInterface{
        public void execute(){
            System.out.println("执行A的execute方法...");
        }
    }
    //代理类的实现
    public class JDKProxy implements InvocationHandler{
    
        /**
         * 要被代理的目标对象
         */
        private A target;
    
        public JDKProxy(A target){
            this.target=target;
        }
    
        /**
         * 创建代理类
         * @return
         */
        public ExInterface createProxy(){
            return (ExInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
        }
    
        /**
         * 调用被代理类(目标对象)的任意方法都会触发invoke方法
         * @param proxy 代理类
         * @param method 被代理类的方法
         * @param args 被代理类的方法参数
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //过滤不需要该业务的方法
            if("execute".equals(method.getName())) {
                //调用前验证权限
                AuthCheck.authCheck();
                //调用目标对象的方法
                Object result = method.invoke(target, args);
                //记录日志数据
                Report.recordLog();
                return result;
            }eles if("delete".equals(method.getName())){
                //.....
            }
            //如果不需要增强直接执行原方法
            return method.invoke(target,args);
        }
    }
    
     //测试验证
     public static void main(String args[]){
          A a=new A();
          //创建JDK代理
          JDKProxy jdkProxy=new JDKProxy(a);
          //创建代理对象
          ExInterface proxy=jdkProxy.createProxy();
          //执行代理对象方法
          proxy.execute();
      } 

    运行结果:

    这里写图片描述

    在A的execute方法里面并没有调用任何权限和日志的代码,也没有直接操作a对象,相反地只是调用了proxy代理对象的方法,最终结果却是预期的,这就是动态代理技术,是不是跟Spring AOP似曾相识?实际上动态代理的底层是通过反射技术来实现,只要拿到A类的class文件和A类的实现接口,很自然就可以生成相同接口的代理类并调用a对象的方法了,关于底层反射技术的实现,暂且不过多讨论,请注意实现java的动态代理是有先决条件的,该条件是目标对象必须带接口,如A类的接口是ExInterface,通过ExInterface接口动态代理技术便可以创建与A类类型相同的代理对象,如下代码演示了创建并调用时利用多态生成的proxy对象:

     A a=new A();
     //创建JDK代理类
     JDKProxy jdkProxy=new JDKProxy(a);
     //创建代理对象,代理对象也实现了ExInterface,通过Proxy实现
     ExInterface proxy=jdkProxy.createProxy();
     //执行代理对象方法
     proxy.execute();
    代理对象的创建是通过Proxy类达到的,Proxy类由Java JDK提供,利用Proxy#newProxyInstance方法便可以动态生成代理对象(proxy),底层通过反射实现的,该方法需要3个参数
    /**
    * @param loader 类加载器,一般传递目标对象(A类即被代理的对象)的类加载器
    * @param interfaces 目标对象(A)的实现接口
    * @param h 回调处理句柄(后面会分析到)
    */
    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
    创建代理类proxy的代码如下:
    public ExInterface createProxy(){
       return (ExInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
        } 

    到此并没结束,因为有接口还是远远不够,代理类(Demo中的JDKProxy)还需要实现InvocationHandler接口,也是由JDK提供,代理类必须实现并重写invoke方法,完全可以把InvocationHandler看成一个回调函数(Callback),Proxy方法创建代理对象proxy后,当调用execute方法(代理对象也实现ExInterface)时,将会回调InvocationHandler#invoke方法,因此我们可以在invoke方法中来控制被代理对象(目标对象)的方法执行,从而在该方法前后动态增加其他需要执行的业务,Demo中的代码很好体现了这点:

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //过滤不需要该业务的方法
        if("execute".equals(method.getName())) {
            //调用前验证权限(动态添加其他要执行业务)
            AuthCheck.authCheck();
    
            //调用目标对象的方法(执行A对象即被代理对象的execute方法)
            Object result = method.invoke(target, args);
    
            //记录日志数据(动态添加其他要执行业务)
            Report.recordLog();
    
            return result;
        }eles if("delete".equals(method.getName())){
         //.....
         return method.invoke(target, args);
        }
        //如果不需要增强直接执行原方法
        return method.invoke(target,args);
    } 

    invoke方法有三个参数:

    • Object proxy :生成的代理对象
    • Method method:目标对象的方法,通过反射调用
    • Object[] args:目标对象方法的参数

    这就是Java JDK动态代理的代码实现过程,小结一下,运用JDK动态代理,被代理类(目标对象,如A类),必须已有实现接口如(ExInterface),因为JDK提供的Proxy类将通过目标对象的类加载器ClassLoader和Interface,以及句柄(Callback)创建与A类拥有相同接口的代理对象proxy,该代理对象将拥有接口ExInterface中的所有方法,同时代理类必须实现一个类似回调函数的InvocationHandler接口并重写该接口中的invoke方法,当调用proxy的每个方法(如案例中的proxy#execute())时,invoke方法将被调用,利用该特性,可以在invoke方法中对目标对象(被代理对象如A)方法执行的前后动态添加其他外围业务操作,此时无需触及目标对象的任何代码,也就实现了外围业务的操作与目标对象(被代理对象如A)完全解耦合的目的。当然缺点也很明显需要拥有接口,这也就有了后来的CGLIB动态代理了

    CGLIB动态代理

    通过CGLIB动态代理实现上述功能并不要求目标对象拥有接口类,实际上CGLIB动态代理是通过继承的方式实现的,因此可以减少没必要的接口,下面直接通过简单案例协助理解(CGLIB是一个开源项目,github网址是:https://github.com/cglib/cglib)。

    //被代理的类即目标对象
    public class A {
        public void execute(){
            System.out.println("执行A的execute方法...");
        }
    }
    
    //代理类
    public class CGLibProxy implements MethodInterceptor {
    
        /**
         * 被代理的目标类
         */
        private A target;
    
        public CGLibProxy(A target) {
            super();
            this.target = target;
        }
    
        /**
         * 创建代理对象
         * @return
         */
        public A createProxy(){
            // 使用CGLIB生成代理:
            // 1.声明增强类实例,用于生产代理类
            Enhancer enhancer = new Enhancer();
            // 2.设置被代理类字节码,CGLIB根据字节码生成被代理类的子类
            enhancer.setSuperclass(target.getClass());
            // 3.//设置回调函数,即一个方法拦截
            enhancer.setCallback(this);
            // 4.创建代理:
            return (A) enhancer.create();
        }
    
        /**
         * 回调函数
         * @param proxy 代理对象
         * @param method 委托类方法
         * @param args 方法参数
         * @param methodProxy 每个被代理的方法都对应一个MethodProxy对象,
         *                    methodProxy.invokeSuper方法最终调用委托类(目标类)的原始方法
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
       //过滤不需要该业务的方法
          if("execute".equals(method.getName())) {
              //调用前验证权限(动态添加其他要执行业务)
              AuthCheck.authCheck();
    
              //调用目标对象的方法(执行A对象即被代理对象的execute方法)
              Object result = methodProxy.invokeSuper(proxy, args);
    
              //记录日志数据(动态添加其他要执行业务)
              Report.recordLog();
    
              return result;
          }else if("delete".equals(method.getName())){
              //.....
              return methodProxy.invokeSuper(proxy, args);
          }
          //如果不需要增强直接执行原方法
          return methodProxy.invokeSuper(proxy, args);
    
        }
    }

    从代码看被代理的类无需接口即可实现动态代理,而CGLibProxy代理类需要实现一个方法拦截器接口MethodInterceptor并重写intercept方法,类似JDK动态代理的InvocationHandler接口,也是理解为回调函数,同理每次调用代理对象的方法时,intercept方法都会被调用,利用该方法便可以在运行时对方法执行前后进行动态增强。关于代理对象创建则通过Enhancer类来设置的,Enhancer是一个用于产生代理对象的类,作用类似JDK的Proxy类,因为CGLib底层是通过继承实现的动态代理,因此需要传递目标对象(如A)的Class,同时需要设置一个回调函数对调用方法进行拦截并进行相应处理,最后通过create()创建目标对象(如A)的代理对象,运行结果与前面的JDK动态代理效果相同。

    public A createProxy(){
        // 1.声明增强类实例,用于生产代理类
        Enhancer enhancer = new Enhancer();
        // 2.设置被代理类字节码,CGLIB根据字节码生成被代理类的子类
        enhancer.setSuperclass(target.getClass());
        // 3.设置回调函数,即一个方法拦截
        enhancer.setCallback(this);
        // 4.创建代理:
        return (A) enhancer.create();
      }

    关于JDK代理技术 和 CGLIB代理技术到这就讲完了,我们也应该明白 Spring AOP 确实是通过 CGLIB或者JDK代理 来动态地生成代理对象,这个代理对象指的就是 AOP 代理类,而 AOP 代理类的方法则通过在目标对象的切入点动态地织入增强处理,从而完成了对目标方法的增强。这里并没有非常深入去分析这两种技术,更倾向于向大家演示Spring AOP 底层实现的最简化的模型代码,Spring AOP内部已都实现了这两种技术,Spring AOP 在使用时机上也进行自动化调整,当有接口时会自动选择JDK动态代理技术,如果没有则选择CGLIB技术,当然Spring AOP的底层实现并没有这么简单,为更简便生成代理对象,Spring AOP 内部实现了一个专注于生成代理对象的工厂类,这样就避免了大量的手动编码,这点也是十分人性化的,但最核心的还是动态代理技术。从性能上来说,Spring AOP 虽然无需特殊编译器协助,但性能上并不优于AspectJ的静态织入,这点了解一下即可。最后给出Spring AOP简化的原理模型如下图:

     参考资料

    http://blog.csdn.net/javazejian/article/details/56267036#基于aspect-spring-aop-开发

  • 相关阅读:
    JavaScrip(二)JavaScrip语法基础
    JavaScrip(一)JavaScrip的写法
    MySQL远程登陆
    JavaScript简介
    Fedora防火墙配置
    sqlalchem表关联(一对多,一对一,多对多)
    常见的SQLALCHEMY列类型
    flask使用配置文件
    算法(一)概述
    pom
  • 原文地址:https://www.cnblogs.com/junzi2099/p/8274924.html
Copyright © 2020-2023  润新知