【啃啊啃 Spring5 源码】细碎三:AOP两种动态代理的区别

spring 中 AOP是基于 “动态代理” 实现,其采用了两种方式:

  1. java代理:采用java内置的代理API实现
  2. cglib代理:采用第三方API实现

本文主要阐述这两种方式的区别

动态代理和静态代理

  • 静态代理:编译时将增强代码植入class文件,因为是编译期进行的增强,所以代码运行时效率比动态代理高。使用Aspect可以实现静态代理。
  • 动态代理:运行时生成代理类并加载,效率比静态代理要低,spring中使用了上文中的两种动态代理的方式来实现代理类的生成。

JDK 动态代理

cglib代理类的实现效率是比 jdk代理类实现效率要高,并且更强大的,但spring中更推荐使用java原生的代理方式,原因在于,第三方lib的实现有太多不确定性,使用java原生的代理方式更加有保障和稳定。

java代理类的实现主要靠Proxy类和InvocationHandler接口来实现。下面直接抛出示例代码demo:

/**
 * jdk 代理的示例demo
 */
public class MyJdkProxy{

    /**
     * 接口
     */
    public interface IHello{
        void sayHello();
    }

    /**
     * IHello接口实现类
     */
    static class Hello implements IHello{
        public void sayHello() {
            System.out.println("Hello world!!");
        }
    }

    /**
     * 自定义InvocationHandler
     */
    static  class HWInvocationHandler implements InvocationHandler {
        //目标对象
        private Object target;
        public HWInvocationHandler(Object target){
            this.target = target;
        }
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("------插入前置通知代码-------------");
            //执行相应的目标方法
            Object rs = method.invoke(target,args);
            System.out.println("------插入后置处理代码-------------");
            return rs;
        }
    }
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //生成$Proxy0的class文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        /*
         * 方式一:
         */
        //获取动态代理类class并加载
        Class proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class);
        //获得代理类的构造函数,并传入参数类型InvocationHandler.class
        Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
        //通过构造函数来创建动态代理对象,将自定义的InvocationHandler实例传入
        IHello iHello = (IHello) constructor.newInstance(new HWInvocationHandler(new Hello()));

        /*
         * 方式二:可看成方式一的语法糖
         */
//        IHello  iHello = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(),  //加载接口的类加载器
//                new Class[]{IHello.class},      //一组接口
//                new HWInvocationHandler(new Hello())); //自定义的InvocationHandler


        //通过代理对象调用目标方法
        iHello.sayHello();
    }
}

代理类的class是在Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class)这个方法执行后被生成,class路径在项目根目录的com\sun\proxy中:

我们反编译下这个代理类class:

public final class $Proxy0
  extends Proxy
  implements MyJdkProxy.IHello
{
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m0;
  
  public $Proxy0(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject)
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final void sayHello()
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final String toString()
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final int hashCode()
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("test.wsz.spring.aop.MyJdkProxy$IHello").getMethod("sayHello", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

从代理类class可以看出,java生成的代理类实现了 被代理对象的接口IHello,同时继承了 Proxy

这个地方解决了我们的一个疑惑:spring为什么要采用两种动态代理的实现方式?

原因就在于,JDK的动态代理只能针对被接口修饰的类,因为代理类$Proxy0必须继承Proxy,Java中的类又是单继承的,所以只能规定委托类Hello必须有实现接口IHello,从而使生成的代理类$Proxy0能够重写委托了中的方法sayHello()

这里梳理下java中代理类的生成步骤

  1. 通过Proxy.getProxyClass()方法生成代理类的class文件并加载。(具体生成步骤见 源码中ProxyClassFactory.apply()
  2. 获取这个class的构造器,传入代理类的逻辑实现类InvocationHandler作为构造函数参数,实例化class获得代理类对象。
  3. 调用代理类对象的对应方法

具体源码就不分析了,可看 深度剖析JDK动态代理机制

CGLIB 动态代理

不多说,直接上demo代码:

/**
 * cglib 代理的示例demo
 */
public class MyCglibProxy {


    /**
     * Hello类
     */
    static class Hello{
        public void sayHello() {
            System.out.println("Hello world!!");
        }
    }

    static class HelloMethodInterceptor implements MethodInterceptor {

        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("------插入前置通知代码-------------");
            //执行相应的目标方法
            Object rs = methodProxy.invokeSuper(o,objects);
            System.out.println("------插入后置处理代码-------------");

            return rs;
        }
    }

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        //代理类基础自 被代理类Hello
        enhancer.setSuperclass(Hello.class);
        //设置回调类,代理逻辑在回调类中
        enhancer.setCallback(new HelloMethodInterceptor());

        //生成代理类
        Hello hello = (Hello) enhancer.create();
        hello.sayHello();
    }

}

生成的代理类class:

public class MyCglibProxy$Hello$$EnhancerByCGLIB$$1716bafc
  extends MyCglibProxy.Hello
  implements Factory
{
  private boolean CGLIB$BOUND;
  public static Object CGLIB$FACTORY_DATA;
  private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
  private static final Callback[] CGLIB$STATIC_CALLBACKS;
  private MethodInterceptor CGLIB$CALLBACK_0;
  private static Object CGLIB$CALLBACK_FILTER;
  private static final Method CGLIB$sayHello$0$Method;
  private static final MethodProxy CGLIB$sayHello$0$Proxy;
  private static final Object[] CGLIB$emptyArgs;
  private static final Method CGLIB$equals$1$Method;
  private static final MethodProxy CGLIB$equals$1$Proxy;
  private static final Method CGLIB$toString$2$Method;
  private static final MethodProxy CGLIB$toString$2$Proxy;
  private static final Method CGLIB$hashCode$3$Method;
  private static final MethodProxy CGLIB$hashCode$3$Proxy;
  private static final Method CGLIB$clone$4$Method;
  private static final MethodProxy CGLIB$clone$4$Proxy;
  
  /* Error */
  static void CGLIB$STATICHOOK1()
  {
  
  }
  
  final void CGLIB$sayHello$0()
  {
    super.sayHello();
  }
  
  public final void sayHello()
  {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    if (this.CGLIB$CALLBACK_0 != null) {
      return;
    }
    super.sayHello();
  }
  
  final boolean CGLIB$equals$1(Object paramObject)
  {
    return super.equals(paramObject);
  }
  
  public final boolean equals(Object paramObject)
  {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
    if (tmp17_14 != null)
    {
      Object tmp41_36 = tmp17_14.intercept(this, CGLIB$equals$1$Method, new Object[] { paramObject }, CGLIB$equals$1$Proxy);
      tmp41_36;
      return tmp41_36 == null ? false : ((Boolean)tmp41_36).booleanValue();
    }
    return super.equals(paramObject);
  }
  
  final String CGLIB$toString$2()
  {
    return super.toString();
  }
  
  public final String toString()
  {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
    if (tmp17_14 != null) {
      return (String)tmp17_14.intercept(this, CGLIB$toString$2$Method, CGLIB$emptyArgs, CGLIB$toString$2$Proxy);
    }
    return super.toString();
  }
  
  final int CGLIB$hashCode$3()
  {
    return super.hashCode();
  }
  
  public final int hashCode()
  {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
    if (tmp17_14 != null)
    {
      Object tmp36_31 = tmp17_14.intercept(this, CGLIB$hashCode$3$Method, CGLIB$emptyArgs, CGLIB$hashCode$3$Proxy);
      tmp36_31;
      return tmp36_31 == null ? 0 : ((Number)tmp36_31).intValue();
    }
    return super.hashCode();
  }
  
  final Object CGLIB$clone$4()
    throws CloneNotSupportedException
  {
    return super.clone();
  }
  
  protected final Object clone()
    throws CloneNotSupportedException
  {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
    if (tmp17_14 != null) {
      return tmp17_14.intercept(this, CGLIB$clone$4$Method, CGLIB$emptyArgs, CGLIB$clone$4$Proxy);
    }
    return super.clone();
  }
  
  /* Error */
  public static MethodProxy CGLIB$findMethodProxy(net.sf.cglib.core.Signature arg0)
  {
    // Byte code:
    //   0: aload_0
    //   1: invokevirtual 119   java/lang/Object:toString   ()Ljava/lang/String;
    //   4: dup
    //   5: invokevirtual 120   java/lang/Object:hashCode   ()I
    //   8: lookupswitch    default:+112->120, -508378822:+52->60, 1535311470:+64->72, 1826985398:+76->84, 1913648695:+88->96, 1984935277:+100->108
    //   60: ldc 122
    //   62: invokevirtual 123  java/lang/Object:equals (Ljava/lang/Object;)Z
    //   65: ifeq +56 -> 121
    //   68: getstatic 116  test/wsz/spring/aop/MyCglibProxy$Hello$$EnhancerByCGLIB$$1716bafc:CGLIB$clone$4$Proxy   Lnet/sf/cglib/proxy/MethodProxy;
    //   71: areturn
    //   72: ldc 125
    //   74: invokevirtual 123  java/lang/Object:equals (Ljava/lang/Object;)Z
    //   77: ifeq +44 -> 121
    //   80: getstatic 49   test/wsz/spring/aop/MyCglibProxy$Hello$$EnhancerByCGLIB$$1716bafc:CGLIB$sayHello$0$Proxy    Lnet/sf/cglib/proxy/MethodProxy;
    //   83: areturn
    //   84: ldc 127
    //   86: invokevirtual 123  java/lang/Object:equals (Ljava/lang/Object;)Z
    //   89: ifeq +32 -> 121
    //   92: getstatic 68   test/wsz/spring/aop/MyCglibProxy$Hello$$EnhancerByCGLIB$$1716bafc:CGLIB$equals$1$Proxy  Lnet/sf/cglib/proxy/MethodProxy;
    //   95: areturn
    //   96: ldc -127
    //   98: invokevirtual 123  java/lang/Object:equals (Ljava/lang/Object;)Z
    //   101: ifeq +20 -> 121
    //   104: getstatic 85  test/wsz/spring/aop/MyCglibProxy$Hello$$EnhancerByCGLIB$$1716bafc:CGLIB$toString$2$Proxy    Lnet/sf/cglib/proxy/MethodProxy;
    //   107: areturn
    //   108: ldc -125
    //   110: invokevirtual 123 java/lang/Object:equals (Ljava/lang/Object;)Z
    //   113: ifeq +8 -> 121
    //   116: getstatic 98  test/wsz/spring/aop/MyCglibProxy$Hello$$EnhancerByCGLIB$$1716bafc:CGLIB$hashCode$3$Proxy    Lnet/sf/cglib/proxy/MethodProxy;
    //   119: areturn
    //   120: pop
    //   121: aconst_null
    //   122: areturn
  }
  
  public MyCglibProxy$Hello$$EnhancerByCGLIB$$1716bafc()
  {
    CGLIB$BIND_CALLBACKS(this);
  }
  
  public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] paramArrayOfCallback)
  {
    CGLIB$THREAD_CALLBACKS.set(paramArrayOfCallback);
  }
  
  public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] paramArrayOfCallback)
  {
    CGLIB$STATIC_CALLBACKS = paramArrayOfCallback;
  }
  
  private static final void CGLIB$BIND_CALLBACKS(Object paramObject)
  {
    1716bafc local1716bafc = (1716bafc)paramObject;
    if (!local1716bafc.CGLIB$BOUND)
    {
      local1716bafc.CGLIB$BOUND = true;
      Object tmp23_20 = CGLIB$THREAD_CALLBACKS.get();
      if (tmp23_20 == null)
      {
        tmp23_20;
        CGLIB$STATIC_CALLBACKS;
      }
      local1716bafc.CGLIB$CALLBACK_0 = (tmp31_28 == null ? tmp31_28 : (MethodInterceptor)((Callback[])tmp23_20)[0]);
    }
  }
  
  public Object newInstance(Callback[] paramArrayOfCallback)
  {
    CGLIB$SET_THREAD_CALLBACKS(paramArrayOfCallback);
    CGLIB$SET_THREAD_CALLBACKS(null);
    return new 1716bafc();
  }
  
  public Object newInstance(Callback paramCallback)
  {
    CGLIB$SET_THREAD_CALLBACKS(new Callback[] { paramCallback });
    CGLIB$SET_THREAD_CALLBACKS(null);
    return new 1716bafc();
  }
  
  /* Error */
  public Object newInstance(Class[] arg1, Object[] arg2, Callback[] arg3)
  {
    // Byte code:
    //   0: aload_3
    //   1: invokestatic 195    test/wsz/spring/aop/MyCglibProxy$Hello$$EnhancerByCGLIB$$1716bafc:CGLIB$SET_THREAD_CALLBACKS    ([Lnet/sf/cglib/proxy/Callback;)V
    //   4: new 2   test/wsz/spring/aop/MyCglibProxy$Hello$$EnhancerByCGLIB$$1716bafc
    //   7: dup
    //   8: aload_1
    //   9: dup
    //   10: arraylength
    //   11: tableswitch    default:+24->35, 0:+17->28
    //   28: pop
    //   29: invokespecial 196  test/wsz/spring/aop/MyCglibProxy$Hello$$EnhancerByCGLIB$$1716bafc:<init>    ()V
    //   32: goto +17 -> 49
    //   35: goto +3 -> 38
    //   38: pop
    //   39: new 202    java/lang/IllegalArgumentException
    //   42: dup
    //   43: ldc -52
    //   45: invokespecial 207  java/lang/IllegalArgumentException:<init>   (Ljava/lang/String;)V
    //   48: athrow
    //   49: aconst_null
    //   50: invokestatic 195   test/wsz/spring/aop/MyCglibProxy$Hello$$EnhancerByCGLIB$$1716bafc:CGLIB$SET_THREAD_CALLBACKS    ([Lnet/sf/cglib/proxy/Callback;)V
    //   53: areturn
  }
  
  public Callback getCallback(int paramInt)
  {
    CGLIB$BIND_CALLBACKS(this);
    switch (paramInt)
    {
    case 0: 
      break;
    }
    return null;
  }
  
  public void setCallback(int paramInt, Callback paramCallback)
  {
    switch (paramInt)
    {
    case 0: 
      this.CGLIB$CALLBACK_0 = ((MethodInterceptor)paramCallback);
      break;
    }
  }
  
  public Callback[] getCallbacks()
  {
    CGLIB$BIND_CALLBACKS(this);
    return new Callback[] { this.CGLIB$CALLBACK_0 };
  }
  
  public void setCallbacks(Callback[] paramArrayOfCallback)
  {
    this.CGLIB$CALLBACK_0 = ((MethodInterceptor)paramArrayOfCallback[0]);
  }
  
  static {}
}

从反编译的class文件可看出,cglib生成的代理类是直接继承被代理类Hello,代理的具体逻辑是通过回调类HelloMethodInterceptor实现。

这里不详细分析cglib的源码,可看:深入理解CGLIB动态代理机制

两种动态代理方式的区别

  1. cglib底层运用了asm这个非常强大的Java字节码生成框架来生成class, 比jdk代理效率要高
  2. jdk代理只能代理被接口修饰的类,而cglib没有这个限制(原因上面已经分析,jdk是动态生成委托类的接口的实现类,cglib是动态生成委托类的子类)

最后补充下:spring中,如果bean实现了接口,则会使用jdk代理方式,否则采用cglib代理方式。也可通过<aop:aspectj-autoproxy proxy-target-class="true"/>配置来强制使用cglib代理方式

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容