• AOP编程之cglib动态代理:进阶一


    cglib实现动态代理的常识

    1、无法代理final修饰的类和方法;

    2、与JDK代理最大的区别是:

      cglib动态代理:在编译后,通过修改字节码,生成新的代理对象proxyObj,此时该proxyObj是被代理类的子类;(关于字节码技术需要引入asm包,后续再深入研究)

      jdk动态代理:通过反射机制实现动态代理,而且被代理的类必须是一个Interface的实现,也就是基于接口编程的;(更详细的了解,请参考文章:待补充

      在代理效率上,就能理解为什么cglib动态代理的效率为啥比jdk动态代理快;

    本文的正题是cglib动态代理的进阶,就不再啰嗦这些肤浅的东西,进入正题:

    1、首先让我们看看cglib的简单实现:

    /**
     * 步骤1:被代理的类 UserDto 
     */ 
    public class UserDto {
        String name;
        String age; 
        public UserDto(){    }
    
        public String getName() {
            return name;
        } 
        public void setName(String name) {
            this.name = name;
        }
        // final 修饰的方法
        public final String getAge() {
            return age;
        }
        public void setAge(String age) {
            this.age = age;
        }
    }
    
    /**
     * 步骤2:实现cglib拦截器
     */
    public class MyCglibInterceptor implements MethodInterceptor {
    
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("intercept:num123");
           // if("void".equals(method.getGenericReturnType().toString())){
           //      methodProxy.invoke(o,objects);
           //      return null;
           // }  
           // return result  = methodProxy.invoke(o,objects);
           return  methodProxy.invokeSuper(o,objects);
        } 
    }
    
    /**
     * 步骤3: 使用cglib动态代理
     */
    public class MainTest {
    
        public static void main(String[] args){
           // cglib代理类class文件存入本地磁盘方便我们反编译查看源码
            System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\cglibCode");
            // 通过CGLIB动态代理获取代理对象的过程
            Enhancer enhancer = new Enhancer();
            // 设置enhancer对象的父类
            enhancer.setSuperclass(UserDto.class);
            // 设置enhancer的回调对象
            enhancer.setCallback(new MyCglibInterceptor());
            // 创建代理对象
            UserDto proxy= (UserDto)enhancer.create();
            // 通过代理对象调用目标方法
            proxy.setAge("123");
            System.out.println(proxy.getAge());
        }
    }
    

    2、精彩的部分来了,通过反编译工具看看UserDto的cglib的代理类(关于如何配置反编译工具,请参考文章:待补充)

      1 public class UserDto$$EnhancerByCGLIB$$cbf83845 extends UserDto implements Factory {
      2     private boolean CGLIB$BOUND;
      3     public static Object CGLIB$FACTORY_DATA;
      4     private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
      5     private static final Callback[] CGLIB$STATIC_CALLBACKS;
      6     private MethodInterceptor CGLIB$CALLBACK_0;
      7     private static Object CGLIB$CALLBACK_FILTER;
      8     private static final Method CGLIB$getName$0$Method;
      9     private static final MethodProxy CGLIB$getName$0$Proxy;
     10     private static final Object[] CGLIB$emptyArgs;
     11     private static final Method CGLIB$setName$1$Method;
     12     private static final MethodProxy CGLIB$setName$1$Proxy;
     13     private static final Method CGLIB$setAge$2$Method;
     14     private static final MethodProxy CGLIB$setAge$2$Proxy;
     15     private static final Method CGLIB$equals$3$Method;
     16     private static final MethodProxy CGLIB$equals$3$Proxy;
     17     private static final Method CGLIB$toString$4$Method;
     18     private static final MethodProxy CGLIB$toString$4$Proxy;
     19     private static final Method CGLIB$hashCode$5$Method;
     20     private static final MethodProxy CGLIB$hashCode$5$Proxy;
     21     private static final Method CGLIB$clone$6$Method;
     22     private static final MethodProxy CGLIB$clone$6$Proxy;
     23 
     24     static void CGLIB$STATICHOOK1() {
     25         CGLIB$THREAD_CALLBACKS = new ThreadLocal();
     26         CGLIB$emptyArgs = new Object[0];
     27         Class var0 = Class.forName("com.mj.app.proxy.UserDto$$EnhancerByCGLIB$$cbf83845");
     28         Class var1;
     29         Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
     30         CGLIB$equals$3$Method = var10000[0];
     31         CGLIB$equals$3$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$3");
     32         CGLIB$toString$4$Method = var10000[1];
     33         CGLIB$toString$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$4");
     34         CGLIB$hashCode$5$Method = var10000[2];
     35         CGLIB$hashCode$5$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$5");
     36         CGLIB$clone$6$Method = var10000[3];
     37         CGLIB$clone$6$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$6");
     38         var10000 = ReflectUtils.findMethods(new String[]{"getName", "()Ljava/lang/String;", "setName", "(Ljava/lang/String;)V", "setAge", "(Ljava/lang/String;)V"}, (var1 = Class.forName("com.mj.app.proxy.UserDto")).getDeclaredMethods());
     39         CGLIB$getName$0$Method = var10000[0];
     40         CGLIB$getName$0$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "getName", "CGLIB$getName$0");
     41         CGLIB$setName$1$Method = var10000[1];
     42         CGLIB$setName$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "setName", "CGLIB$setName$1");
     43         CGLIB$setAge$2$Method = var10000[2];
     44         CGLIB$setAge$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "setAge", "CGLIB$setAge$2");
     45     }
     46 
     47     final String CGLIB$getName$0() {
     48         return super.getName();
     49     }
     50 
     51     public final String getName() {
     52         MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
     53         if (this.CGLIB$CALLBACK_0 == null) {
     54             CGLIB$BIND_CALLBACKS(this);
     55             var10000 = this.CGLIB$CALLBACK_0;
     56         }
     57 
     58         return var10000 != null ? (String)var10000.intercept(this, CGLIB$getName$0$Method, CGLIB$emptyArgs, CGLIB$getName$0$Proxy) : super.getName();
     59     }
     60 
     61     final void CGLIB$setName$1(String var1) {
     62         super.setName(var1);
     63     }
     64 
     65     public final void setName(String var1) {
     66         MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
     67         if (this.CGLIB$CALLBACK_0 == null) {
     68             CGLIB$BIND_CALLBACKS(this);
     69             var10000 = this.CGLIB$CALLBACK_0;
     70         }
     71 
     72         if (var10000 != null) {
     73             var10000.intercept(this, CGLIB$setName$1$Method, new Object[]{var1}, CGLIB$setName$1$Proxy);
     74         } else {
     75             super.setName(var1);
     76         }
     77     }
     78 
     79     final void CGLIB$setAge$2(String var1) {
     80         super.setAge(var1);
     81     }
     82 
     83     public final void setAge(String var1) {
     84         MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
     85         if (this.CGLIB$CALLBACK_0 == null) {
     86             CGLIB$BIND_CALLBACKS(this);
     87             var10000 = this.CGLIB$CALLBACK_0;
     88         }
     89 
     90         if (var10000 != null) {
     91             var10000.intercept(this, CGLIB$setAge$2$Method, new Object[]{var1}, CGLIB$setAge$2$Proxy);
     92         } else {
     93             super.setAge(var1);
     94         }
     95     }
     96 
     97     final boolean CGLIB$equals$3(Object var1) {
     98         return super.equals(var1);
     99     }
    100 
    101     public final boolean equals(Object var1) {
    102         MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    103         if (this.CGLIB$CALLBACK_0 == null) {
    104             CGLIB$BIND_CALLBACKS(this);
    105             var10000 = this.CGLIB$CALLBACK_0;
    106         }
    107 
    108         if (var10000 != null) {
    109             Object var2 = var10000.intercept(this, CGLIB$equals$3$Method, new Object[]{var1}, CGLIB$equals$3$Proxy);
    110             return var2 == null ? false : (Boolean)var2;
    111         } else {
    112             return super.equals(var1);
    113         }
    114     }
    115 
    116     final String CGLIB$toString$4() {
    117         return super.toString();
    118     }
    119 
    120     public final String toString() {
    121         MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    122         if (this.CGLIB$CALLBACK_0 == null) {
    123             CGLIB$BIND_CALLBACKS(this);
    124             var10000 = this.CGLIB$CALLBACK_0;
    125         }
    126 
    127         return var10000 != null ? (String)var10000.intercept(this, CGLIB$toString$4$Method, CGLIB$emptyArgs, CGLIB$toString$4$Proxy) : super.toString();
    128     }
    129 
    130     final int CGLIB$hashCode$5() {
    131         return super.hashCode();
    132     }
    133 
    134     public final int hashCode() {
    135         MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    136         if (this.CGLIB$CALLBACK_0 == null) {
    137             CGLIB$BIND_CALLBACKS(this);
    138             var10000 = this.CGLIB$CALLBACK_0;
    139         }
    140 
    141         if (var10000 != null) {
    142             Object var1 = var10000.intercept(this, CGLIB$hashCode$5$Method, CGLIB$emptyArgs, CGLIB$hashCode$5$Proxy);
    143             return var1 == null ? 0 : ((Number)var1).intValue();
    144         } else {
    145             return super.hashCode();
    146         }
    147     }
    148 
    149     final Object CGLIB$clone$6() throws CloneNotSupportedException {
    150         return super.clone();
    151     }
    152 
    153     protected final Object clone() throws CloneNotSupportedException {
    154         MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    155         if (this.CGLIB$CALLBACK_0 == null) {
    156             CGLIB$BIND_CALLBACKS(this);
    157             var10000 = this.CGLIB$CALLBACK_0;
    158         }
    159 
    160         return var10000 != null ? var10000.intercept(this, CGLIB$clone$6$Method, CGLIB$emptyArgs, CGLIB$clone$6$Proxy) : super.clone();
    161     }
    162 
    163     public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
    164         String var10000 = var0.toString();
    165         switch(var10000.hashCode()) {
    166         case -1184972270:
    167             if (var10000.equals("setName(Ljava/lang/String;)V")) {
    168                 return CGLIB$setName$1$Proxy;
    169             }
    170             break;
    171         case -508378822:
    172             if (var10000.equals("clone()Ljava/lang/Object;")) {
    173                 return CGLIB$clone$6$Proxy;
    174             }
    175             break;
    176         case -201623326:
    177             if (var10000.equals("setAge(Ljava/lang/String;)V")) {
    178                 return CGLIB$setAge$2$Proxy;
    179             }
    180             break;
    181         case 1218144844:
    182             if (var10000.equals("getName()Ljava/lang/String;")) {
    183                 return CGLIB$getName$0$Proxy;
    184             }
    185             break;
    186         case 1826985398:
    187             if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
    188                 return CGLIB$equals$3$Proxy;
    189             }
    190             break;
    191         case 1913648695:
    192             if (var10000.equals("toString()Ljava/lang/String;")) {
    193                 return CGLIB$toString$4$Proxy;
    194             }
    195             break;
    196         case 1984935277:
    197             if (var10000.equals("hashCode()I")) {
    198                 return CGLIB$hashCode$5$Proxy;
    199             }
    200         }
    201 
    202         return null;
    203     }
    204 
    205     public UserDto$$EnhancerByCGLIB$$cbf83845() {
    206         CGLIB$BIND_CALLBACKS(this);
    207     }
    208 
    209     public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
    210         CGLIB$THREAD_CALLBACKS.set(var0);
    211     }
    212 
    213     public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
    214         CGLIB$STATIC_CALLBACKS = var0;
    215     }
    216 
    217     private static final void CGLIB$BIND_CALLBACKS(Object var0) {
    218         UserDto$$EnhancerByCGLIB$$cbf83845 var1 = (UserDto$$EnhancerByCGLIB$$cbf83845)var0;
    219         if (!var1.CGLIB$BOUND) {
    220             var1.CGLIB$BOUND = true;
    221             Object var10000 = CGLIB$THREAD_CALLBACKS.get();
    222             if (var10000 == null) {
    223                 var10000 = CGLIB$STATIC_CALLBACKS;
    224                 if (CGLIB$STATIC_CALLBACKS == null) {
    225                     return;
    226                 }
    227             }
    228 
    229             var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
    230         }
    231 
    232     }
    233 
    234     public Object newInstance(Callback[] var1) {
    235         CGLIB$SET_THREAD_CALLBACKS(var1);
    236         UserDto$$EnhancerByCGLIB$$cbf83845 var10000 = new UserDto$$EnhancerByCGLIB$$cbf83845();
    237         CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
    238         return var10000;
    239     }
    240 
    241     public Object newInstance(Callback var1) {
    242         CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
    243         UserDto$$EnhancerByCGLIB$$cbf83845 var10000 = new UserDto$$EnhancerByCGLIB$$cbf83845();
    244         CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
    245         return var10000;
    246     }
    247 
    248     public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
    249         CGLIB$SET_THREAD_CALLBACKS(var3);
    250         UserDto$$EnhancerByCGLIB$$cbf83845 var10000 = new UserDto$$EnhancerByCGLIB$$cbf83845;
    251         switch(var1.length) {
    252         case 0:
    253             var10000.<init>();
    254             CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
    255             return var10000;
    256         default:
    257             throw new IllegalArgumentException("Constructor not found");
    258         }
    259     }
    260 
    261     public Callback getCallback(int var1) {
    262         CGLIB$BIND_CALLBACKS(this);
    263         MethodInterceptor var10000;
    264         switch(var1) {
    265         case 0:
    266             var10000 = this.CGLIB$CALLBACK_0;
    267             break;
    268         default:
    269             var10000 = null;
    270         }
    271 
    272         return var10000;
    273     }
    274 
    275     public void setCallback(int var1, Callback var2) {
    276         switch(var1) {
    277         case 0:
    278             this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
    279         default:
    280         }
    281     }
    282 
    283     public Callback[] getCallbacks() {
    284         CGLIB$BIND_CALLBACKS(this);
    285         return new Callback[]{this.CGLIB$CALLBACK_0};
    286     }
    287 
    288     public void setCallbacks(Callback[] var1) {
    289         this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
    290     }
    291 
    292     static {
    293         CGLIB$STATICHOOK1();
    294     }
    295 }

    从下面的代理类源码可知 :cglib的代理形式,实质是UserDto的子类,此外还有以下几个关键特点:

    特点一:每个代理的方法都生成对应的两个方法,而且所有方法都是final类型;

        // 直接调用父类的getName方法;
    final
    String CGLIB$getName$0() { return super.getName(); } // 逻辑:先判断方法拦截器(就是上面刚定义的拦截器MyCglibInterceptor)是否有定义,如果没有,就直接调用父类的getName方法;所以在AOP编程中,也就可以在拦截其中实现before,after等等; public final String getName() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (this.CGLIB$CALLBACK_0 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } return var10000 != null ? (String)var10000.intercept(this, CGLIB$getName$0$Method, CGLIB$emptyArgs, CGLIB$getName$0$Proxy) : super.getName(); }

    特点二:UserDto中用final修饰的方法:public final void  getAge()在代理类中找不到了,原因就是:final修饰的方法,无法被继承;

    其它特点后续在补充;

    接下来,我们再进一步窥探cglib动态代理拦截器实现逻辑:拦截器中的细节,就以 proxy.setAge("123");为例:

    第一步:初始化并创建UserDto的增强代理类的实例proxy

    // 通过CGLIB动态代理获取代理对象的过程
    Enhancer enhancer = new Enhancer();
    // 设置enhancer对象的父类
    enhancer.setSuperclass(UserDto.class);
    // 设置enhancer的回调对象
    enhancer.setCallback(new MyCglibInterceptor());
    // 创建代理对象
    UserDto proxy= (UserDto)enhancer.create();
    第二步:执行 proxy.setAge("123");根据上面的分析,它实际是UserDto的子类的实例;所该方法的实现如下代码所示
    public final void setAge(String var1) {
            MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
            if (this.CGLIB$CALLBACK_0 == null) {
                CGLIB$BIND_CALLBACKS(this);
                var10000 = this.CGLIB$CALLBACK_0;
            }
    
            if (var10000 != null) {
    // 如果有实现MethodIntercept接口,就会在此处调用你实现的拦截器的方法; var10000.intercept(this, CGLIB$setAge$2$Method, new Object[]{var1}, CGLIB$setAge$2$Proxy); } else { super.setAge(var1); } }

    第三步:由于我们有实现MethodIntercept接口,var10000就是我们的拦截器MyCglibInterceptor的实例,接着通过执行var10000.intercept(...)进入我们的拦截器

    public class MyCglibInterceptor implements MethodInterceptor {
    
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("intercept:num123");
           // if("void".equals(method.getGenericReturnType().toString())){
           //      methodProxy.invoke(o,objects);
           //      return null;
           // }  
           // return result  = methodProxy.invoke(o,objects);
           return  methodProxy.invokeSuper(o,objects);
        } 
    } 

    注意:1、springframework实现AOP的原理中,就在此时和aspectj进行集成,在拦截其中实现:before,after等等;

    2、拦截器中的关键就是invoke(...),invokeSuper(..)

    3、拦截器的参数解释:待补充

    第四步:执行invoke,此时需要注意对invoke的实现由两种方式,1:invoke,2:invokeSuper;

    第五步:最终通过:invokeSuper()调用被代理类UserDto的方法setAge;再逆向返回结果

    最后执行完方法:proxy.setAge("123");

    -------------------------------------------

    初稿,后续完善

    问题1: cglib如何通过修改字节码,为被代理类,创建超级代理类,该技术的实现原理?

    问题2:jdk代理的实现原理?

  • 相关阅读:
    JAVA回调机制(转)
    单例模式之饿汉懒汉模式
    TOMCAT目录结构
    Hibernate Component的用法
    【转】单例模式完全剖析
    EhCache使用详解
    【转】jar包详解和METAINF作用
    使用 Spring 2.5 注释驱动的 IoC 功能
    vc中,制作在任务栏隐藏图标的mfc程序
    打开网页,打开外部应用程序
  • 原文地址:https://www.cnblogs.com/outpointexception/p/10915254.html
Copyright © 2020-2023  润新知