zoukankan      html  css  js  c++  java
  • java两种动态代理方式的理解

      要理解动态代理,不妨先来看看一个静态代理的例子。

      一.静态代理

      以一个电商项目的例子来说明问题,比如我定义了一个订单的接口IOrder,其中有一个方法时delivery,代码如下。

      

    package com.xdx.learn;
    
    public interface IOrder {
        void delivery();//发货
        void confirmReceipt();//确认收货
    }

      Order类实现了该接口,如下所示。

      

    package com.xdx.learn;
    
    public class Order implements IOrder {
        public void delivery() {
            System.out.println("delivering the commodity");
        }
    
        public void confirmReceipt() {
            System.out.println("confirmReceipt the commodity");
            
        }
        
    }

      假如写完这个项目后,老板想要在order类的每个方法中加入操作日志的功能,或者性能统计。可是我又不想再更改原来的order类,特别是在接手别人的项目的时候,我们特别不喜欢去修改既有的代码,一方面修改容易导致未知的问题,另外一方面,修改有时候不如自己重写快。

      此时我们可以为Order类写一个代理类,对原来的Order类进行一层简单的包装,以达到目的。

    package com.xdx.learn;
    
    public class OrderProxy implements IOrder {
        private Order order;//注入原来的Order类
        
        public Order getOrder() {
            return order;
        }
    
        public void setOrder(Order order) {
            this.order = order;
        }
    
        public void delivery() {
            System.out.println("do some log");//添加一些关于日志的操作
            order.delivery();//目标类(被代理类)的业务逻辑
    
        }
    
        public void confirmReceipt() {
            System.out.println("do some log");//添加一些关于日志的操作
            order.confirmReceipt();//目标类(被代理类)的业务逻辑
        }
    
    }

      调用代理类的方法如下所示。

        public static void main(String args[]){
            Order order=new Order();//目标类对象
            OrderProxy proxy=new OrderProxy();//代理类对象
            proxy.setOrder(order);//注入
            proxy.delivery();//带有日志功能的发货操作
            proxy.confirmReceipt();//带有日志功能的确认收货操作
        }

      从上面的例子可以看出,静态代理主要的好处是不需要修改原来的目标类,实现解耦。

      但是假如目标类里面有很多方法呢?或者假如我要为更多的目标类添加日志功能呢?那我必须为每个类都定义一个静态代理类,并且为每个类的每个方法写一个对应的加入了日志管理功能的方法。显然这是一项巨大的工程。这时候静态代理已经不能满足我们的需求了,我们需要的是动态代理。

      二.动态代理

      首先我们需要对动态代理和静态代理的概念做一下解释,所谓的静态代理就是代理类在运行之前就写好了,比如我们上面写好的OrderProxy这个类。与之对应的,动态代理的 代理类对象是当程序运行的时候才产生的,也就是说我们事先并没有定义一个代理类,而是需要用到的时候它才产生。动态代理又分为两种,一种是基于jdk的,一种是基于Cglib的。他们的实现原理有所不同。

      1.基于JDK的动态代理。

      我简单的描述一下整个过程涉及到的各方类。

      IOrder:接口

      Order:目标类,我们称为target。也就是被代理的类。

       然后我们需要一个实现了InvocationHandler接口的类OrderHander,在这个类中,我们生成一个代理对象并与该hander对象进行绑定,并且利用反射机制(主要是Method的invoke方法)来调用目标类的方法。且在方法中织入增强逻辑(例如日志管理功能)。这样说很抽象,我们来看实际的Hander类吧。

      

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class OrderHander implements InvocationHandler {
        private Object target;// 目标类对象,即被代理的对象
    
        public OrderHander(Object target) {
            this.target = target;
        }
    
        public Object Bind() {
            // 绑定操作,生成一个target的代理类对象,该代理类与目标类实现相同的接口,所以需要传入接口的参数。
        ,并且将该代理对象与this,也就是该OrderHander对象绑定
    return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } /** * 该方法是InvocationHandler的唯一的方法,当与该OrderHander类对象绑定的代理类的方法被调用的时候, * 就会执行该方法,并且传入代理对象,方法对象,以及方法的参数。这样我们才可以用反射机制来调用目标类的方法。 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("do some log"); method.invoke(target, args);// 注意这边的第一个参数target,即目标类,而非代理类。因为执行的是目标类的业务逻辑。
         System.out.println(method); return null; } }
    OrderHander 实现了InvocationHandler 接口,查看jdk源码,有一段是这样的。
    /**
     * {@code InvocationHandler} is the interface implemented by
     * the <i>invocation handler</i> of a proxy instance.
     *
     * <p>Each proxy instance has an associated invocation handler.
     * When a method is invoked on a proxy instance, the method
     * invocation is encoded and dispatched to the {@code invoke}
     * method of its invocation handler.

      简单翻译一下:InvocationHandler是一个接口,它被一个proxy instance(代理类对象)的invocation handler(调用 处理器)所实现。这句话虽简单,但是表述得很明确,invocation handler(调用 处理器)是属于某个代理类对象。这也是我们为什么在OrderHander 中将目标类的代理类与自身绑定的原因。

      下一句话:每个代理对象都有一个相关联的,当一个方法被代理对象调用的时候,这个被调用的方法会被编码并且分发到与这个代理类对象关联的invocation handler(调用 处理器)的invoke方法处执行。

      也就是说,代理类是依附于调用 处理器存在的,在调用它的方法的时候,并不会实际执行,而是转发到它所对应的调用处理器中的invoke方法执行。

      这也解释了,我们在OrderHander类中的invoke方法中不仅能织入增强(日志管理),而且通过method.invoke()方法调用了目标类的实际方法。

      我们来为上述调用处理器写一个main方法,证实我们的理论。

      

    public static void main(String args[]){
            Order order=new Order();//目标对象
            System.out.println(order);
            OrderHander orderHander=new OrderHander(order);//调用处理器对象
            IOrder orderProxy=(IOrder) orderHander.Bind();//生成代理对象,注意这里是IOrder对象,可以理解为多态吧,并且与orderHander绑定了
            orderProxy.delivery();
            orderProxy.confirmReceipt();
        }

      上述代码的执行结果如下:

      com.xdx.learn.Order@15db9742
      public abstract void com.xdx.learn.IOrder.delivery()
      do some log
      delivering the commodity
      public abstract void com.xdx.learn.IOrder.confirmReceipt()
      do some log
      confirmReceipt the commodity

      其中public abstract void com.xdx.learn.IOrder.delivery()和public abstract void com.xdx.learn.IOrder.confirmReceipt()是我在invoke方法中system.out.println(method)。可看到这个menthod对象是接口处method。这也解释得通为什么我们再获取一个代理对象的时候采用IOrder,即原始的接口类来定义。

      我们可以近似简单的理解:代理类跟目标类是同一个接口的不同实现类,所以可以用多态的形式来调用代理类的方法的方法,而转发给其handle对象来执行,在hander对象的invoke方法中,我们除了织入增强,还调用了method.invoke()方法,因为此处的method对象是接口的方法对象,所以同样可以传入目标类target这个对象作为参数。

      由此可见,基于jdk的动态代理依赖于接口的实现,这就要求我们必须为每个目标类都定义一个接口,才能采用这种代理方式。

      2.基于Cglib的动态代理方式

      基于Cglib的动态代理方式不是采用实现接口的方式去构造一个代理类,而是直接为代理类实现一个增强过后的子类,子类的生成运用了字节码技术,整个过程主要使用了Enhancer、MethodInterceptor、MethodProxy等类。

       首先需要引入Cglib的相关jar包,maven配置如下。

      

    <!-- 动态代理cglib -->
            <dependency>
                <groupId>cglib</groupId>
                <artifactId>cglib</artifactId>
                <version>3.2.5</version>
            </dependency>

      下面还是以一个例子来阐述Cglib动态代理的原理。

      目标类:Order类的代码如下:

      

    package com.xdx.learn;
    
    public class Order {
        public void delivery(){
            System.out.println("delivering the commodity");
        }
        public void confirmRecipet(){
            System.out.println("confirmRecipeting the commodity");
        }
    
    }

      新建一个CglibInterceptor,实现了MethodInterceptor接口,其代码如下所示。

      

    package com.xdx.learn;
    
    import java.lang.reflect.Method;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    public class CglibInterceptor implements MethodInterceptor {
        // 增强器,它的作用是对目标类对象增强,生成一个代理类,并且指定一个回调对象,这个回调对象一般是一个MethodInterceptor对象。
        // 通过MethodInterceptor对象的拦截功能,即intercept方法,我们可以做一些增强工作。
        private Enhancer enhancer = new Enhancer();
    
        // 以下方法生成一个代理对象,它以目标对象的子类的形式存在
        public Object getProxy(Class clazz) {
            enhancer.setSuperclass(clazz);
            enhancer.setCallback(this);// 设置当前MethodInterceptor对象为enhancer对象的回调对象,这个步骤至关重要
            return enhancer.create();// 调用create()方法生成一个子类(代理类,增强类)。
        }
    
        /**
         * 所有代理类调用的方法都会被这个方法所拦截,前提是该代理类设置的回调对象是该CglibInterceptor对象本身
         * 
         * @param obj
         *            代理类,也就是被增强的类
         * @param method
         *            被拦截的方法
         * @param args
         *            方法参数
         * @param proxy
         *            用于调用目标类(也就是父类)的方法
         */
        public Object intercept(Object obj, Method method, Object[] args,
                MethodProxy proxy) throws Throwable {
            System.out.println("do some log");
            System.out.println(obj.getClass().getName());
            System.out.println(method);// System.out.println("使用MethodProxy对象调用父类的方法");
            proxy.invokeSuper(obj, args);
            return null;
        }
    
    }

      Enhancer类可以对目标类进行增强,通过字节码技术生成目标类的一个子类对象,也就是代理类对象。并且指定了Callback对象,CallBack对象一般为MethodInterceptor类对象,在上述代码中,我们设为this。也就是当前CglibInterceptor对象。我们来看源码中关于

    Enhancer类的两段描述。

    /**
     * Generates dynamic subclasses to enable method interception. This
     * class started as a substitute for the standard Dynamic Proxy support
     * included with JDK 1.3, but one that allowed the proxies to extend a
     * concrete base class, in addition to implementing interfaces. The dynamically
     * generated subclasses override the non-final methods of the superclass and
     * have hooks which callback to user-defined interceptor
     * implementations.
     * <p>
     * The original and most general callback type is the {@link MethodInterceptor}, which
     * in AOP terms enables "around advice"--that is, you can invoke custom code both before
     * and after the invocation of the "super" method. In addition you can modify the
     * arguments before calling the super method, or not call it at all.
     * <p>

      大概翻译:该类可以为目标类生成动态的子类,从而进行方法的拦截。它是基于JDK的动态代理的一种补充,弥补了JDK动态代理只能通过实现接口来创建代理类的不足。该动态生成的的子类重写了父类(目标类)的非finla方法(因为final方法是不能重写的),并且,它与用户自定义的拦截器(interceptor)之间通过callback方法产生了关联(hooks )。

      最常用的callback类型是MethodInterceptor类的实例对象,MethodInterceptor对象在AOP语境下可以实现around advice(环绕增强),所谓的环绕增强就是在父类(目标类)方法的调用前后都可以加入自己的代码。

      简而言之:Enhancer通过字节码技术生成目标类的子类(代理类),并且与一个MethodInterceptor对象产生了挂钩。

      那MethodInterceptor对象又是怎么实现方法的增强的呢?我们还是去看看MethodInterceptor类的源码。

      

    /**
     * General-purpose {@link Enhancer} callback which provides for "around advice".
     * @author Juozas Baliuka <a href="mailto:baliuka@mwm.lt">baliuka@mwm.lt</a>
     * @version $Id: MethodInterceptor.java,v 1.8 2004/06/24 21:15:20 herbyderby Exp $
     */
    public interface MethodInterceptor
    extends Callback
    {
        /**
         * All generated proxied methods call this method instead of the original method.
         * The original method may either be invoked by normal reflection using the Method object,
         * or by using the MethodProxy (faster).
         * @param obj "this", the enhanced object
         * @param method intercepted Method
         * @param args argument array; primitive types are wrapped
         * @param proxy used to invoke super (non-intercepted method); may be called
         * as many times as needed
         * @throws Throwable any exception may be thrown; if so, super method will not be invoked
         * @return any value compatible with the signature of the proxied method. Method returning void will ignore this value.
         * @see MethodProxy
         */    
        public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                                   MethodProxy proxy) throws Throwable;
    
    }

      大概翻译:

      类上的注释:该类主要目的是为Enhancer对象提供一个回调对象,用于方法的增强。这段话几乎是与Enhancer那边的说明遥相呼应的。

      方法上的注释:所有调用代理类的方法都会被替换成调用该intercept方法,其实就是一个拦截的操作。可以通过普通的反射机制(Method方法)来调用原始方法,或者通过MethodProxy对象来调用原始方法,后者速度更快。

      参数:obj:代理类对象,method:被拦截的方法,args:参数,proxy:一个MethodProxy对象,它是为了我们能调用目标类(父类)的方法。

      这些参数都是Enhancer对象传递过来的,这样Enhancer就与MethodInterceptor实现了关联。

      最后,我们为Cglib动态代理写一个调用方法,如下所示。

      

        public static void main(String args[]){
            CglibInterceptor interceptor=new CglibInterceptor();
            Order order=(Order) interceptor.getProxy(Order.class);//生成一个增强对象,并且已经指定了interceptor为callback对象
            order.delivery();
            order.confirmRecipet();
        }

      上述代码的运行结果如下:

      do some log
      com.xdx.learn.Order$$EnhancerByCGLIB$$64d5dd4b
      public void com.xdx.learn.Order.delivery()
      delivering the commodity
      do some log
      com.xdx.learn.Order$$EnhancerByCGLIB$$64d5dd4b
      public void com.xdx.learn.Order.confirmRecipet()
      confirmRecipeting the commodity

      可以看到确实实现了增强,也看到了增强后的代理类是com.xdx.learn.Order$$EnhancerByCGLIB$$64d5dd4b。

      三.两种方法的比较

      1.基于JDK的代理依附于接口,所以必须为每个目标类创建接口,这点比较麻烦。

      2.基于CGLIB的动态代理采用子类的方式生成代理,并且它的性能会比基于JDK的代理来得高。但是在生成代理的时候会比较慢,所以如果需要代理的目标类是单例的情况下,推荐这种代理方式。

      3.其实代理类都不真正去调用执行方法,而是交给第三方对象去调用执行方法,并且在执行的过程中织入增强。基于JDK的代理是交给InvocationHandler对象的invoke方法,而基于CGLIB的代理则是交给MethodInterceptor的intercept方法。前提是,二者都要在生成代理类的时候与相应的第三方对象产生关联。

      4.二者都有使用局限,基于JDK的代理方式无法为非public方法,static方法实现增强(因为接口都是public方法,接口中也不能有static方法)。而CGLIb是通过覆盖父类方法来实现代理的,所以一切不能被重写的方法都无法被增强,即final,static,private方法都不能被增强。

  • 相关阅读:
    thinkphp3.2 无法加载模块
    php 使用 wangeditor3 图片上传
    nginx 配置 server
    oracle练手(一)
    Oracle练习(一)
    java运算符优先级
    数据库(mysql和oracle)
    java实现4种内部排序
    mysql-----分库分表
    NIO总结-----Buffer
  • 原文地址:https://www.cnblogs.com/roy-blog/p/7900761.html
Copyright © 2011-2022 走看看