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方法都不能被增强。

原文地址:https://www.cnblogs.com/roy-blog/p/7900761.html