揭秘JDK动态代理

作者: 数齐 | 来源:发表于2017-02-07 10:33 被阅读117次

    代理在我们日常开发过程中有着很重要的角色,它可以处理一些日志记录,权限控制,事务等。主要分为静态代理和动态代理,他们的主要区别是有没有源文件,静态代理是有源文件的,应用起来比较直观但是稍有浪费且不智能,所以我们一般使用的是动态代理,他是在程序运行期间动态生成的,程序加载动态生成的字节码生成Class对象加以运用,灵活性高。
    下面我们介绍一下jdk的动态代理。
    首先声明一个接口IHello.java

    public interface IHello {
        void sayHello(String name);
    }
    

    在创建一个接口的具体的实现类HelloService.java

    public class HelloService implements IHello {
        @Override
        public void sayHello(String name) {
            System.out.println("hello "+name);
        }
    }
    

    使用jdk的动态代理,代理的逻辑必须写在InvocationHandler的实现类中,当调用接口的具体方法时,实际上是调用的InvocationHandler中的invoke方法,这个在后面会有详细的解释。
    HelloHandler.java

    public class HelloHandler implements InvocationHandler {
        /**
         * 调用的具体的对象
         */
        private Object concreteObj;
    
        public HelloHandler(IHello hello) {
            concreteObj = hello;
        }
    
        /**
         * @param proxy   生成的代理对象的回填
         * @param method
         * @param args
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("proxy:"+proxy.getClass().getName());
            long start = System.currentTimeMillis();
            Object o = method.invoke(concreteObj, args);
            System.out.println("cost time :" + (System.currentTimeMillis() - start) + "ms");
            return o;
        }
    }
    

    需要的类都准备好了,我们来测试一下

        public static void main(String[] args) {
            IHello hello = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), new Class[]{IHello.class}, new HelloHandler(new HelloService()));
            hello.sayHello("test");
        }
    

    结果为:

    proxy:com.sun.proxy.$Proxy0
    hello test
    cost time :0ms
    

    我们这个动态代理的作用就是统计一下方法调用的时间。
    我们发现了两个问题:

    1. 这个HelloHandler中invoke方法第一个参数proxy是怎么填入的并且他的class的name为何为com.sun.proxy.$Proxy0
    2. 我调用的是IHello中的sayHello方法,为啥他会走到HelloHandler中的invoke方法。

    我们带着这两个疑问继续看起来。
    我们先看一下Proxy是如何newProxyInstance的

    110.png

    下面我们看一下生成的字节码是怎样的

        public static void main(String[] args) {
             createProxyClassFile();
        }
    
        public static void createProxyClassFile() {
            String name = "ProxyHello";
            byte[] data = ProxyGenerator.generateProxyClass(name, new Class[]{IHello.class});
            try {
                FileOutputStream out = new FileOutputStream(name + ".class");
                out.write(data);
                out.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    创建的文件这这样的

    public final class ProxyHello extends Proxy implements IHello {
        private static Method m1;
        private static Method m3;
        private static Method m2;
        private static Method m0;
    
        public ProxyHello(InvocationHandler var1) throws  {
            super(var1);
        }
    
        public final boolean equals(Object var1) throws  {
            try {
                return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final void sayHello(String var1) throws  {
            try {
                super.h.invoke(this, m3, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
                m3 = Class.forName("com.pajk.homecare.unittest.proxy.IHello").getMethod("sayHello", new Class[]{Class.forName("java.lang.String")});
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }
    

    根据这个文件,我们看一下,当调用代理的sayHello方法时,调用的是handler的invoke方法,传入的第一个参数是this,所以就是Proxy的实例了。所以上面提出的两个问题得解。

    相关文章

      网友评论

        本文标题:揭秘JDK动态代理

        本文链接:https://www.haomeiwen.com/subject/kbrpittx.html