zoukankan      html  css  js  c++  java
  • 动态代理-JDK/CGLIB

    转载自:https://www.jianshu.com/p/95970b089360

    一个程序员Developer,他会开发code,他调试debug。

     
     

    程序员有很多分类,其中有Java程序员JavaDeveloper,他会开发Java代码,会调试Java代码。
     

    但是呢,有个叫Zack的程序员它在开发之前,会祈祷一下,这样他开发的代码就不会有bug。

    Zack的这种“特异功能”是后天练出来的,并没有哪种程序员有这种特性。虽然我们也可以定义一个拥有这样特性的程序员,但是拥有各种乱七八糟特性的程序千千万。我们什么时候才能定义完,而能保证不漏呢?

    其实我们没有必要去定义他,因为他是后天养成的,我们应该在这个程序员的成长期去实现这个特性,而不是在他出生之前定义。

    我们来看下代码是怎么实现的


     

    如果Zack只是一个普通的Java程序员,那么他的开发结果是
    Zack is coding java
    Zack is debugging java

    但是真正的Zack(代理后)
    Zack is praying for the code!
    Zack is coding java
    Zack's have no bug!No need to debug!

    Proxy.newProxyInstance()

    回看下上面是如何使用动态代理的使用。生成一个实例对象,然后用Proxy的newInstance方法对这个实例对象代理生成一个代理对象。


     

    这里有一个非常关键的人,也是比较少人去理解它的。为什么要传zack的类加载和zack的接口呢?
    有没有留意到zackProxy的类型是Developer接口,而不是一个实现类。因为zack在被代理后生成的对象,并不属于Developer接口的任何一个实现类。但是它是基于Developer接口和zack的类加载代理出来的。

    看下newProxyInstance()的接口定义


     

    这三个参数具体的含义来看看注解是怎么描述的


     
     
    • loder,选用的类加载器。因为代理的是zack,所以一般都会用加载zack的类加载器。
    • interfaces,被代理的类所实现的接口,这个接口可以是多个。
    • h,绑定代理类的一个方法。

    loder和interfaces基本就是决定了这个类到底是个怎么样的类。而h是InvocationHandler,决定了这个代理类到底是多了什么功能。所以动态代理的内容重点就是这个InvocationHandler。

    InvocationHandler

     
     

    根据注解描述可知,InvocationHandler作用就是,当代理对象的原本方法被调用的时候,会绑定执行一个方法,这个方法就是InvocationHandler里面定义的内容,同时会替代原本方法的结果返回。

    InvocationHandler接收三个参数

    • proxy,代理后的实例对象。
    • method,对象被调用方法。
    • args,调用时的参数。

    在上面的例子里,


     

    如果最后的return语句改成

    return method.invoke(proxy, agrs);
    

    invoke的对象不是zack,而是proxy,根据上面的说明猜猜会发生什么?
    是的,会不停地循环调用。因为proxy是代理类的对象,当该对象方法被调用的时候,会触发InvocationHandler,而InvocationHandler里面又调用一次proxy里面的对象,所以会不停地循环调用。并且,proxy对应的方法是没有实现的。所以是会循环的不停报错

    动态代理的原理

    通过上面的讲解,相信大家对动态代理的使用理解得比较深刻了。那动态代理到底是怎么实现的呢,我们来看看源码其中关键的地方。
    在newProxyInstance()发放中有这样几段。


     

    其实大概就是把接口复制出来,通过这些接口和类加载器,拿到这个代理类cl。然后通过反射的技术复制拿到代理类的构造函数(这部分代码在Class类中的getConstructor0方法),最后通过这个构造函数new个一对象出来,同时用InvocationHandler绑定这个对象。

    CGLIB 动态代理:
    JDK 动态代理,JDK 动态代理的类必须实现一个接口,而且生成的代理类是其接口的实现类,也就是被代理的类的兄弟类,由JDK内部实现,
    cglib代理的类,无需强制实现接口,其生成的代理类 是 被代理类的子类,并且重写的被代理类的方法,而且需要额外的引入Jar

    简单实现:
    计算一个 sql 调用总时间

    代码逻辑:

    创建一个需要被代理的类 SqlService:

    public class SqlService {
    public void executeSql1() throws InterruptedException {
    System.out.println("Sql 开始执行.....");
    Thread.sleep(1000);
    System.out.println("Sql 执行结束.....");
    }
    }

    创建代理类: SqlFacadeCglib

    public class SqlFacadeCglib implements MethodInterceptor {
    
    /**
    * 被代理的对象
    */
    private Object target;
    
    public SqlFacadeCglib(Object target) {
    this.target = target;
    }
    
    /**
    * 实现回调方法
    * @param obj 代理的对象
    * @param method 被代理对象的方法
    * @param args 参数集合
    * @param proxy 生成的代理类的方法
    * @return
    * @throws Throwable
    */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    // 开始执行时间
    Long startTime = System.currentTimeMillis();
    // //调用业务类(父类中)的方法
    Object result = proxy.invokeSuper(obj, args);
    // 执行结束
    Long endTime = System.currentTimeMillis();
    System.out.println(target.getClass().getName()+"执行executeSql耗时"+(endTime-startTime)+"ms");
    return result;
    }
    }

    创建一个Test:

    public class Test {
    
    public static void main(String[] args) throws InterruptedException, IOException {
    // 将class 文件保存
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:/mycode/Git/spring-mvc/learn/design-pattern-learn/src/main/java/com/lot/cglibproxy");
    SqlService sqlService = new SqlService();
    SqlFacadeCglib sqlFacadeCglib = new SqlFacadeCglib(sqlService);
    
    //创建加强器,用来创建动态代理类
    Enhancer enhancer = new Enhancer();
    //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
    enhancer.setSuperclass(sqlService.getClass());
    
    //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
    enhancer.setCallback(sqlFacadeCglib);
    // 创建动态代理类对象并返回
    SqlService sqlServiceProxy = (SqlService)enhancer.create();
    // 调用
    sqlServiceProxy.executeSql1();
    
    }
    }


    执行结果:
    控制台打印:

    CGLIB debugging enabled, writing to 'F:/mycode/Git/spring-mvc/learn/design-pattern-learn/src/main/java/com/lot/cglibproxy'
    Sql 开始执行.....
    Sql 执行结束.....
    com.lot.cglibproxy.SqlService执行executeSql耗时1018ms

    Debug 时 看到 sqlServiceProxy 定义:

    根据Debug的显示 结合 System.setProperty() 写入文件的位置,我们找到 生成的代理类:SqlService¥¥EnhancerByCGLIB¥¥ecb184ce : (简略后代码) :
    public class SqlService$$EnhancerByCGLIB$$ecb184ce extends SqlService implements Factory {
    
    static void CGLIB$STATICHOOK1() {
    CGLIB$THREAD_CALLBACKS = new ThreadLocal();
    // 参数集合
    CGLIB$emptyArgs = new Object[0];
    Class var0 = Class.forName("com.lot.cglibproxy.SqlService$$EnhancerByCGLIB$$ecb184ce");
    Class var1;
    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());
    CGLIB$equals$1$Method = var10000[0];
    CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
    CGLIB$toString$2$Method = var10000[1];
    CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
    CGLIB$hashCode$3$Method = var10000[2];
    CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
    CGLIB$clone$4$Method = var10000[3];
    CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
    // 被代理类的 executeSql1() 方法
    CGLIB$executeSql1$0$Method = ReflectUtils.findMethods(new String[]{"executeSql1", "()V"}, (var1 = Class.forName("com.lot.cglibproxy.SqlService")).getDeclaredMethods())[0];
    // 生成代理类的 executeSql1() 方法
    CGLIB$executeSql1$0$Proxy = MethodProxy.create(var1, var0, "()V", "executeSql1", "CGLIB$executeSql1$0");
    }
    public final void executeSql1() throws InterruptedException {
    MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    if (this.CGLIB$CALLBACK_0 == null) {
    CGLIB$BIND_CALLBACKS(this);
    var10000 = this.CGLIB$CALLBACK_0;
    }
    
    if (var10000 != null) {
    // 执行 SqlFacadeCglib.intercept() 方法
    var10000.intercept(this, CGLIB$executeSql1$0$Method, CGLIB$emptyArgs, CGLIB$executeSql1$0$Proxy);
    } else {
    super.executeSql1();
    }
    }

    总结:

    在 Test.main() 方法中,使用 Enhancer 加强器和 我们自己定义的类SqlService、SqlFacadeCglib组合,再通过enhancer.create()创建代理类。从代理类的源码中,我们可以看到代理类其实是我们业务类的子类
    当我们在执行 sqlServiceProxy.executeSql1()时 实际上是访问 CGLIB生成的代理类的 executeSql1() 方法,然后在调用 SqlFacadeCglib.intercept() 做一些公共逻辑处理,其中 proxy.invokeSuper(obj, args) 这段代码是执行 我们业务逻辑类中的方法:SqlService.executeSql1()
    其实动态代理的目的就是帮我们整合业务逻辑类,和共有的方法,并生成代理类,大大简化了我们开发工作量,其中 Spring 中对动态代理有很深的使用。


    原文链接:https://blog.csdn.net/zhangyong01245/article/details/90644933

  • 相关阅读:
    用数据泵技术实现逻辑备份Oracle 11g R2 数据泵技术详解(expdp impdp)
    用mysql实现类似于oracle dblink的功能
    统计1的个数
    转置字符串,其中单词内的字符需要正常
    经典排序之归并排序
    公共子序列与公共子串问题
    placement new (转)
    数组排序组合最小数字
    实现两个数相加不用四则运算
    操作系统中作业、线程、进程、内存管理、垃圾回收以及缓存等概念
  • 原文地址:https://www.cnblogs.com/heyjia/p/11343793.html
Copyright © 2011-2022 走看看