戏说代理模式和Java动态代理

作者: 楚云之南 | 来源:发表于2015-12-23 17:46 被阅读3577次

    代理

    所谓代理,是为某一个实例的访问进行代理和控制。最好的例子是艺人和经纪人的关系,比如沟通档期是一个接口,那么艺人通常会通过经纪人去和第三方沟通档期相关的事情,这样的好处是,沟通档期的经纪人就会限制第三方对艺人的其它事情的打扰,只有沟通档期的请求才会被转发到艺人,其它事务都会通过 沟通档期接口进行限制。

    对于演艺公司来说,静态代理就是在一开始为每一个艺人安排一个经纪人,这个经纪人专门负责一个艺人的相关事情。后来公司的管理层发现只有少数大明星的经纪人的事情比较复杂,但是对很多二线 三线的艺人来说,经纪人的事情可能基本上都差不多,但是公司却为每个人都指定了经纪人,导致成本较高(在编程里面表现为很多模板代码)。为了降低成本,公司改变了为每一个艺人都安排一个经纪人的方式,而是在有第三方来沟通艺人档期时,动态安排一个经纪人来为艺人服务,这就是动态代理。

    静态代理比较简单,这里不展开描述,而JDK是从1.3版本开始支持动态代理,下面一起看看它的源码。如果你还不明白什么是代理 什么是代理类,建议先google一下。

    Java动态代理源码分析

    动态代理例子

    首先我们还是通过一个简单的例子来说明动态代理是怎么一回事。

    public class DynamicProxy {
    public static void main(String[] args) {
        Object proxyInstance = Proxy.newProxyInstance(MyInterface.class.getClassLoader(),
                new Class[]{
                        MyInterface.class
                },
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                        if(method.getName().equals("sum")) {
                            return (int)args[0]+(int)args[1];
                        }else if(method.getName().equals("returnMySelf")){
                            return proxy;
                        }
                        return null;
                    }
                }
        );
    
        MyInterface myInterface = (MyInterface) proxyInstance;
        System.out.println(myInterface.sum(1,2));
    }
    
    public static interface MyInterface {
        int sum(int arg1,int arg2);
        MyInterface returnMySelf();
    }
    }
    

    可以看到,我们通过newProxyInstance就产生了一个MyInterface的实例,然后就可以通过myInterface.sum(1,2)来调用sum()方法,然后处理的结果就是我们在InvocationHandler中处理的逻辑,是不是很神奇?下面我们一步步来分析源码是怎么做到的。

    newProxyInstance方法

    Proxy是实现动态代理的主要类,它提供来产生代理类和代理类实例的方法:newProxyInstance (ClassLoader loader, Class[] interfaces, InvocationHandler h),看看代码

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        //权限判断等,忽略
        //生成代理类
        Class<?> cl = getProxyClass0(loader, intfs);
        //调用构造器,生成实例
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            //各种异常处理
        }
    }
    

    getProxyClass0(loader, intfs)中看看:

     private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
         //一个代理类最多能够实现65535个接口
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
    
        // 这里Proxy做了一次缓存,如果之前生成过这个Classloader和interfaces的代理类,那么这里直接返回
    
      //否则新生成类的字节码文件
       byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces);
                try {
                        //将字节码加载到JVM
                        proxyClass = defineClass0(loader, proxyName,
                        proxyClassFile, 0, proxyClassFile.length);
                  }
            retrun proxyClass;
    }
    

    至此,我们生成了一个代理类,然后只需要将它实例化就可以了,可以看到代码中我们把参数InvocationHandler传入了构造器中,这说明我们生成的代理类必然包含了一个接受InvocationHandler的构造器,稍后我们会来分析这个生成的类。

    newProxyInstance这个方法实际上做了两件事:第一,创建了一个新的类【代理类】,这个类实现了Class[] interfaces中的所有接口,并通过你指定的ClassLoader将生成的类的字节码加载到JVM中,创建Class对象;第二,以你传入的InvocationHandler作为参数创建一个代理类的实例并返回。

    代理类的字节码

    上文已经说明了,代理类是通过ProxyGenerator.generateProxyClass(proxyName, interfaces);生成的,这个方法会根据指定的包名和接口,生成一个代理类,我们来测试一下吧。

    public class GenerateClass {
    
    static interface Inter {
        int sum(int arg1,int arg2);
    }
    
    public static void main(String[] args) throws IOException {
        String path = File.separator+"Users"+File.separator+"pengliang"+File.separator+"InterImplProxy.class";
        //  String path = "c:"+File.separator+"ProxyImpl.class";
        //很简单,生成一个实现了Inter接口的代理类的字节码
        byte[] classCode = ProxyGenerator.generateProxyClass("com.chuyun.InterImplProxy.",new Class[] {Inter.class});
        //字节码写入本地
        FileOutputStream fileOutputStream = new FileOutputStream(path);
        fileOutputStream.write(classCode);
        fileOutputStream.close();
    }
    }
    

    我们反编译一下这个InterImplProxy.class文件(常见的反编译工具有:JD-GUI),我直接使用IDEA的插件来打开这个class文件,看到如下代码:

      package com.chuyun.InterImplProxy;
    
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      import java.lang.reflect.Proxy;
      import java.lang.reflect.UndeclaredThrowableException;
      import proxy.GenerateClass.Inter;
    
      //实现了Inter 并继承自Proxy ,Proxy有一个接受InvocationHandler的构造器
      public final class InterImplProxy extends Proxy implements Inter {
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m0;
        static {
        try {
            //反射获取这些函数描述
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("proxy.GenerateClass$Inter").getMethod("sum", new Class[]{Integer.TYPE, Integer.TYPE});
            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());
        }
      }
    
      public InterImplProxy(InvocationHandler var1) throws  {
        super(var1);
      }
    
    public final boolean equals(Object var1) throws  {
        try {
            //将所有方法的调用转发给InvocationHandler的invoke方法
            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 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 sum(int var1, int var2) throws  {
        try {
            return ((Integer)super.h.invoke(this, m3, new Object[]{Integer.valueOf(var1), Integer.valueOf(var2)})).intValue();
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }
    
    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);
        }
    }
    

    }

    ok,至此,是不是感觉动态代理也是一个很简单的东西?唯一的区别是我们静态代理中的代理类是由程序猿一开始定义的,而动态代理则是再运行时,通过InvocationHandler来调用我们发送的调用。

    总结

    1. 动态代理不支持代理抽象类 ,原因嘛,你看看上面生成的代码,代理类默认已经extends ProxyJava总不允许多重继承;
    2. 动态代理生成的类如果实现的接口都是public,那么这个代理类就处于默认包中(最顶层包,没有包名);如果实现了包权限的接口,那么包名就是这个包权限所在的包名。
    3. 类名字格式为:$ProxyN其中N为Proxy的计数器。之前我们说了,如果某一次动态代理的ClassLoader和 接口个数和顺序完全一样,则Proxy不会生成新的代理类。
    4. InvocationHandlerinvoke方法中的第一个参数就是这个代理类的实例,主要的用途呢就是为了解决有的接口返回的对象就是本身,见例子一中的MyInterface returnMySelf();;但是这里有个需要注意的坑:
      **
      永远不要在InvocationHandlerinvoke里面调用第一个参数proxy的任何方法,因为这样会导致invoke循环无数次调用,最终stackoverflow
      **

    相关文章

      网友评论

        本文标题:戏说代理模式和Java动态代理

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