zoukankan      html  css  js  c++  java
  • JDK动态代理,此次之后,永生难忘

    出自:http://www.cnblogs.com/dreamroute/p/5273888.html#3839242

      首先感谢"神一样的存在"的文章!

    动态代理,这个词在Java的世界里面经常被提起,尤其是对于部分(这里强调“部分”二字,因为有做了一两年就成大神的,实力强的令人发指,这类人无疑是非常懂动态代理这点小伎俩的)做了一两年新人来说,总是摸不清楚来龙去脉,一两年是个坎,为什么是一两年,才入门的新人可能对这东西没什么感觉,没到这一步,做了很久开发的人显然是明白这其中原理的,而做了一两年的,知其然而不知其所以然,所以一两年工作经验的人很多是很茫然的。

      那么,这里就相对比较比较深入一点的介绍JDK动态代理的原理。这样子介绍完,明白了其中的道理,我相信你会永远记得JDK动态代理的思想。顺带一句,cglib做的事儿和JDK动态代理做的事儿从结局上来说差不多,方式不太一样。

      1、先从JDK的源代码说起,动态代理这部分源码,Oracle版本和OpenJDK的源码是不太一样的,貌似Oracle版本最核心那点东西没开源,F3进去我反正是找不到,我也懒得去找,但是原理都是一致的,这里就挑选OpenJDK的。

      我们回顾一下JDK动态代理,先说宏观原理,相信都懂,使用JDK动态代理最常见,至少对于我来说就是Spring的AOP部分,并且是AOP部分的声明式事务部分。

      a、定义一个接口Car:

    public interface Car {
        void drive(String driverName, String carName);
    }

       b、定义接口Car的一个实现类Audi:

    public class Audi implements Car {
    
        @Override
        public void drive(String driverName, String carName) {
            System.err.println("Audi is driving... " + "driverName: " + driverName + ", carName" + carName);
        }
    }

      c、定义一个动态调用的控制器CarHandler:

    public class CarHandler implements InvocationHandler {
        
        private Car car;
        
        public CarHandler(Car car) {
            this.car = car;
        }
        
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.err.println("before");
            method.invoke(car, args);
            System.err.println("after");
            return null;
        }
    
    }

      d、测试类ProxyTest:

    public class ProxyTest {
        
        @Test
        public void proxyTest() throws Exception {
            Car audi = (Car) Proxy.newProxyInstance(Car.class.getClassLoader(), new Class<?>[] {Car.class}, new CarHandler(new Audi()));
            audi.drive("name1", "audi");
        }
    }

      e、输出结果:

    before
    Audi is driving... driverName: name1, carNameaudi
    after

      上面这段,相信大家都懂,也明白原理,这就是所谓的知其然,但是不一定都能知其所以然。接下来就解释下“知其所以然”。

      进入到Proxy类的newProxyInstance方法:

        public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            if (h == null) {
                throw new NullPointerException();
            }
    
            /*
             * Look up or generate the designated proxy class.
             */
            Class<?> cl = getProxyClass(loader, interfaces);
    
            /*
             * Invoke its constructor with the designated invocation handler.
             */
            try {
                Constructor cons = cl.getConstructor(constructorParams);
                return cons.newInstance(new Object[] { h });
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString());
            } catch (IllegalAccessException e) {
                throw new InternalError(e.toString());
            } catch (InstantiationException e) {
                throw new InternalError(e.toString());
            } catch (InvocationTargetException e) {
                throw new InternalError(e.toString());
            }
        }

      关键的3行:

    // 创建代理类
    Class<?> cl = getProxyClass(loader, interfaces);
    // 实例化代理对象
    Constructor cons = cl.getConstructor(constructorParams);

      返回的是代理类的实例化对象。接下来的调用就很清晰了。

      那么,JDK动态代理最核心的关键就是这个方法:

    Class<?> cl = getProxyClass(loader, interfaces);

      进入该方法,这个方法很长,前面很多都是铺垫,在方法的最后调用了一个方法:

    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                        proxyName, interfaces);

      这个方法就是产生代理对象的方法。我们先不看前后,只关注这一个方法,我们自己来写一个该方法:

    public class ProxyTest {
        
        @SuppressWarnings("resource")
        @Test
        public void proxyTest() throws Exception {
            byte[] bs = ProxyGenerator.generateProxyClass("AudiImpl", new Class<?>[] {Car.class});
            new FileOutputStream(new File("d:/AudiImpl.class")).write(bs);
        }
    }

      于是,我们就在D盘里面看到了一个叫做AudiImpl.class的文件,对该文件进行反编译,得到下面这个类:

    public final class AudiImpl extends Proxy implements Car {
    
        private static final long serialVersionUID = 5351158173626517207L;
    
        private static Method m1;
        private static Method m3;
        private static Method m0;
        private static Method m2;
    
        public AudiImpl(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 drive(String paramString1, String paramString2) {
            try {
                this.h.invoke(this, m3, new Object[] { paramString1, paramString2 });
                return;
            } 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);
            }
        }
    
        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);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals",
                        new Class[] { Class.forName("java.lang.Object") });
                m3 = Class.forName("com.mook.core.service.Car").getMethod("drive",
                        new Class[] { Class.forName("java.lang.String"), Class.forName("java.lang.String") });
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            } catch (NoSuchMethodException localNoSuchMethodException) {
                throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
            } catch (ClassNotFoundException localClassNotFoundException) {
                throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
            }
        }
    }

      这个时候,JDK动态代理所有的秘密都暴露在了你的面前,当我们调用drive方法的时候,实际上是把方法名称传给控制器,然后执行控制器逻辑。这就实现了动态代理。Spring AOP有两种方式实现动态代理,如果基于接口编程,默认就是JDK动态代理,否则就是cglib方式,另外spring的配置文件里面也可以设置使用cglib来做动态代理,关于二者的性能问题,网上也是众说纷纭,不过我个人的观点,性能都不是问题,不太需要去纠结这一点性能问题。

      事实上,如果你明白这一点,当你去阅读mybatis源码的时候是很有帮助的,mybatis的接口方式做方法查询就充分利用了这里的JDK动态代理。否则如果不明白原理,看mybatis的源码的接口方式是很费劲的,当然了,这只是mybatis利用动态代理的冰山一角,要完全看懂mybaits源码还有其他的许多难点,比如mybatis是以xml文件来配置sql语句的。

  • 相关阅读:
    js自动提交按钮
    win7关机命令
    php中var_export与var_dump的区别分析
    string2array($value);
    Swiper Usage&&API
    在PC上测试移动端网站和模拟手机浏览器的5大方法
    jQuery Mobile 连接外部连接或切换动画
    强烈推荐240多个jQuery插件提供下载
    eclipse 总是提示文件下载
    增加字段关联插件 For PHPCMS V9 免费版
  • 原文地址:https://www.cnblogs.com/onlymate/p/9233129.html
Copyright © 2011-2022 走看看