Java设计模式-代理模式之动态代理(附源码分析)

Java设计模式-代理模式之动态代理(附源码分析)

动态代理概念及类图

上一篇中介绍了静态代理,动态代理跟静态代理一个最大的区别就是:动态代理是在运行时刻动态的创建出代理类及其对象。上篇中的静态代理是在编译的时候就确定了代理类具体类型,如果有多个类需要代理,那么就得创建多个。还有一点,如果Subject中新增了一个方法,那么对应的实现接口的类中也要相应的实习该方法,不符合设计模式原则。

动态代理的做法:在运行时刻,可以动态创建出一个实现了多个接口的代理类。每个代理类的对象都会关联一个表示内部处理逻辑的InvocationHandler接 口的实现。当使用者调用了代理对象所代理的接口中的方法的时候,这个调用的信息会被传递给InvocationHandler的invoke方法。在 invoke方法的参数中可以获取到代理对象、方法对应的Method对象和调用的实际参数。invoke方法的返回值被返回给使用者。这种做法实际上相 当于对方法调用进行了拦截。

类图如下所示:

上面类图中使用的JDK中的Proxy类,所以是需要要办法来告诉Proxy类需要做什么,不能像静态代理一样,将代码放到Proxy类中,因为现在Proxy不是直接实现的。既然这样的代码不能放在Proxy类中,那么就需要一个InvocationHandler,InvocationHandler的工作就是响应代理的任何调用。


动态代理实现过程

具体有如下四步骤:

  • 通过实现 InvocationHandler 接口创建自己的调用处理器;
  • 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
  • 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
  • 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

    一个具体的例子

    接着上面的类图和静态代理中的例子,我们分别创建Subject和RealSubject

  • Subject
    package ProxyMode;
    
    /*
     * 抽象接口,对应类图中的Subject
     *
     */
    
    public interface Subject {
    
        public void SujectShow();
    
    }
    
    
  • RealSubject
    package ProxyMode;
    
    public class RealSubject implements Subject{
    
        @Override
        public void SujectShow() {
            // TODO Auto-generated method stub
            System.out.println("杀人是我指使的,我是幕后黑手!By---"+getClass());
    
        }
    
    }
    
    
  • 建立InvocationHandler用来响应代理的任何调用
    package ProxyMode;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class ProxyHandler implements InvocationHandler {
    
        private Object proxied;   
    
          public ProxyHandler( Object proxied )
          {
            this.proxied = proxied;
          }   
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
    
            System.out.println("准备工作之前:");
    
            //转调具体目标对象的方法
              Object object=   method.invoke( proxied, args);
    
             System.out.println("工作已经做完了!");
             return object;
        }
    
    }
    
    
  • 动态代理类测试,这个代理类中再也不用实现Subject接口,可以动态的获得RealSubject接口中的方法
    package ProxyMode;
    
    import java.lang.reflect.Proxy;
    
    public class DynamicProxy  {
    
        public static void main( String args[] )
          {
            RealSubject real = new RealSubject();
            Subject proxySubject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(),
             new Class[]{Subject.class},
             new ProxyHandler(real));
    
            proxySubject.SujectShow();;
    
          }
    }
    

    测试结果

    准备工作之前:
    杀人是我指使的,我是幕后黑手!By---class ProxyMode.RealSubject
    工作已经做完了!
    
    

    Proxy和InvocationHandler重要部分源码分析

    java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

    清单 1. Proxy 的静态方法
    // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器,比如上面代码中的ProxyHandler
    static InvocationHandler getInvocationHandler(Object proxy) 
    
    // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
    static Class getProxyClass(ClassLoader loader, Class[] interfaces) 
    
    // 方法 3:该方法用于判断指定类对象是否是一个动态代理类
    static boolean isProxyClass(Class cl) 
    
    // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
    static Object newProxyInstance(ClassLoader loader, Class[] interfaces,
        InvocationHandler h)
    

    下面重点看看newPRoxyInstance方法:

    
       public static Object newProxyInstance(ClassLoader loader,
                Class<?>[] interfaces,
                InvocationHandler h)
                throws IllegalArgumentException { 
    
        // 检查 h 不为 空,否则抛异常
        if (h == null) {
            throw new NullPointerException();
        } 
    
        // 获得与制定类装载器和一组接口相关的代理类类型对象
        Class cl = getProxyClass(loader, interfaces); 
    
        // 通过反射获取构造函数对象并生成代理类实例
        try {
            Constructor cons = cl.getConstructor(constructorParams);
            return (Object) 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());
        }
    }
    

    看这个方法的三个参数

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
    
  • loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
  • interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
  • h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

    从上面JDK源码中可以看出getProxyClass方法才是newProxyInstance方法中最重要的,该方法负责为一组接口动态地生成代理类类型对象。下面开始解析proxy中的getProxyClass方法

    该方法总共可以分为四个步骤:

  • 对这组接口进行一定程度的安全检查,包括检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的,还会检查确保是 interface 类型而不是 class 类型。

    这个步骤通过一个循环来完成,检查通过后将会得到一个包含所有接口名称的字符串数组,记为 String[] interfaceNames

     for (int i = 0; i < interfaces.length; i++) {
    
                // 验证类加载程 序 解 析 该接口到同一类对象的名称。
                String interfaceName = interfaces[i].getName();
                Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(interfaceName, false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != interfaces[i]) {
                    throw new IllegalArgumentException(
                        interfaces[i] + " is not visible from class loader");
                }
    
                // 验证类对象真正代表一个接口
    
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
    
                //验证这个接口是不是重复的
                if (interfaceSet.contains(interfaceClass)) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
                interfaceSet.add(interfaceClass); //interfaceset是一个hashset集合
    
                interfaceNames[i] = interfaceName;
            }
    
    
  • 从 loaderToCache 映射表中获取以类装载器对象为关键字所对应的缓存表,如果不存在就创建一个新的缓存表并更新到 loaderToCache。

    缓存表是一个 HashMap 实例,正常情况下它将存放键值对(接口名字列表,动态生成的代理类的类对象引用)。当代理类正在被创建时它会临时保存(接口名字列表,pendingGenerationMarker)。标记 pendingGenerationMarke 的作用是通知后续的同类请求(接口数组相同且组内接口排列顺序也相同)代理类正在被创建,请保持等待直至创建完成。

     synchronized (cache) {
    do {
        // 以接口名字列表作为关键字获得对应 cache 值
        Object value = cache.get(key);
        if (value instanceof Reference) {
            proxyClass = (Class) ((Reference) value).get();
        }
        if (proxyClass != null) {
            // 如果已经创建,直接返回,这里非常重要,如果已经创建过代理类,那么不再创建
            return proxyClass;
        } else if (value == pendingGenerationMarker) {
            // 代理类正在被创建,保持等待
            try {
                cache.wait();
            } catch (InterruptedException e) {
            }
            // 等待被唤醒,继续循环并通过二次检查以确保创建完成,否则重新等待
            continue;
        } else {
            // 标记代理类正在被创建
            cache.put(key, pendingGenerationMarker);
            // break 跳出循环已进入创建过程
            break;
    } while (true);
    }
    
  • 动态创建代理类的类对象。

    首先是确定代理类所在的包,其原则如前所述,如果都为 public 接口,则包名为空字符串表示顶层包;如果所有非 public 接口都在同一个包,则包名与这些接口的包名相同;如果有多个非 public 接口且不同包,则抛异常终止代理类的生成。确定了包后,就开始生成代理类的类名,同样如前所述按格式“$ProxyN”生成。

    // 动态地生成代 理类的字节码数组
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces);
    try {
        // 动态地定义新生成的代理类
        proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0,
            proxyClassFile.length);
    } catch (ClassFormatError e) {
        throw new IllegalArgumentException(e.toString());
    } 
    
    // 把生成的代理类的类对象记录进 proxyClasses 表
    proxyClasses.put(proxyClass, null);
    
    

    到了这里,其实generateProxyClass方法也是一个重点,但是generateProxyClass的方法代码跟踪不了,位于并未公开的 sun.misc 包,有若干常量、变量和方法以完成这个神奇的代码生成的过程,但是 sun 并没有提供源代码以供研读

  • 结尾部分

    根据结果更新缓存表,如果成功则将代理类的类对象引用更新进缓存表,否则清楚缓存表中对应关键值,最后唤醒所有可能的正在等待的线程。

    
     synchronized (cache) {
                    if (proxyClass != null) {
                        cache.put(key, new WeakReference<Class<?>>(proxyClass));
                    } else {
                        cache.remove(key);
                    }
                    cache.notifyAll();
                }
    

    java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

    InvocationHandler 的核心方法,我们最关心的是Invoke方法为什么会被调用,见下面分析:

    // 该方法负责集中处理动态代理类上的所 有方法调用。
    //第一个参数既是代理类实例,
    //第二个参数是被调用的方法对象
    // 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
    
    Object invoke(Object proxy, Method method, Object[] args)
    
    

    每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象(参见 newProxyInstance 的第三个参数)。

    很多人肯定跟我一样,我们在Handler中调用的method.invoke方法中并没有显示的调用invoke方法,只是在newProxyInstance中应用了一个handler对象,有了上面关于newProxyInstance的源码分析,我们知道了 newproxyinstance生成了一个$Proxy0类代理。当调用Subjectshow()方法时,其实调用的$Proxy0的SubjectShow()方法,从而调用父类Proxy中传进来第三个参数(h)的的Invoke方法。

    //这个方法是 Proxy源码中的
      protected Proxy(InvocationHandler h) {
            this.h = h;
        }
    
    

    来看NewProxyInstance方法生成的$Proxy0代理类的源码

    public final class $Proxy0 extends Proxy implements Subject {
        private static Method m1;
        private static Method m0;
        private static Method m3;
        private static Method m2;
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals",
                        new Class[] { Class.forName("java.lang.Object") });
    
                m0 = Class.forName("java.lang.Object").getMethod("hashCode",
                        new Class[0]);
    
                m3 = Class.forName("***.RealSubject").getMethod("request",
                        new Class[0]);
    
                m2 = Class.forName("java.lang.Object").getMethod("toString",
                        new Class[0]);
    
            } catch (NoSuchMethodException nosuchmethodexception) {
                throw new NoSuchMethodError(nosuchmethodexception.getMessage());
            } catch (ClassNotFoundException classnotfoundexception) {
                throw new NoClassDefFoundError(classnotfoundexception.getMessage());
            }
        } //static
    
        public $Proxy0(InvocationHandler invocationhandler) {
            super(invocationhandler);
        }
    
        @Override
        public final boolean equals(Object obj) {
            try {
                return ((Boolean) super.h.invoke(this, m1, new Object[] { obj })) .booleanValue();
            } catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        @Override
        public final int hashCode() {
            try {
                return ((Integer) super.h.invoke(this, m0, null)).intValue();
            } catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final void SubjectShow() {
            try {
                super.h.invoke(this, m3, null); //就是这个地方  调用h.invoke()
                return;
            } catch (Error e) {
            } catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        @Override
        public final String toString() {
            try {
                return (String) super.h.invoke(this, m2, null);
            } catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    }
    
    

    从上面的$Proxy0中找到方法SubjectSHow()方法,我们可以看到中间调用了父类Proxy的参数Handler h的invoke方法,也就调用了ProxyHandler中的invoke()方法,还可以看到¥Proxy0还代理了equals()、hashcode()、tostring()这三个方法,至此动态代理实现机制就很清楚了

  • 参考文章:http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/

    时间: 2024-10-07 06:00:27

    Java设计模式-代理模式之动态代理(附源码分析)的相关文章

    Java设计模式-代理模式之动态代理(附源代码分析)

    Java设计模式-代理模式之动态代理(附源代码分析) 动态代理概念及类图 上一篇中介绍了静态代理,动态代理跟静态代理一个最大的差别就是:动态代理是在执行时刻动态的创建出代理类及其对象. 上篇中的静态代理是在编译的时候就确定了代理类详细类型.假设有多个类须要代理.那么就得创建多个. 另一点,假设Subject中新增了一个方法,那么相应的实现接口的类中也要相应的实现这些方法. 动态代理的做法:在执行时刻.能够动态创建出一个实现了多个接口的代理类.每一个代理类的对象都会关联一个表示内部处理逻辑的Inv

    cocos2d-x 委托模式的巧妙运用——附源码(一)

    先来说一下委托模式是什么,下面的内容摘要自维基百科: 委托模式是软件设计模式中的一项基本技巧.在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理.委托模式是一项基本技巧,许多其他的模式,如状态模式.策略模式.访问者模式本质上是在更特殊的场合采用了委托模式.委托模式使得我们可以用聚合来替代继承. 简单的Java例子 在此例中,类模拟打印机Printer拥有针式打印机RealPrinter的实例,Printer拥有的方法print()将处理转交给RealPrint

    cocos2d-x 委托模式的巧妙运用——附源码(二)

    转载请注明出处:http://blog.csdn.net/hust_superman/article/details/38292265,谢谢. 继上一篇将了委托类的具体实现后,这篇来将一下如何在游戏中使用实现的委托类.也就是如何在游戏中来调用委托类来完成一些功能.具体的应用场景和应用层会在下面介绍. 先来看一看游戏demo实现的具体图片,demo比较简单,但是资源齐全,拿到源码后可以在源码的基础上继续完善demo做出一款真正的游戏.好了,老规矩,先上图再说: 游戏中点击播放按钮后会进入游戏主界面

    java io系列03之 ByteArrayOutputStream的简介,源码分析和示例(包括OutputStream)

    前面学习ByteArrayInputStream,了解了“输入流”.接下来,我们学习与ByteArrayInputStream相对应的输出流,即ByteArrayOutputStream.本章,我们会先对ByteArrayOutputStream进行介绍,在了解了它的源码之后,再通过示例来掌握如何使用它. 转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_03.html ByteArrayOutputStream 介绍 ByteArrayOutpu

    java io系列02之 ByteArrayInputStream的简介,源码分析和示例(包括InputStream)

    我们以ByteArrayInputStream,拉开对字节类型的“输入流”的学习序幕.本章,我们会先对ByteArrayInputStream进行介绍,然后深入了解一下它的源码,最后通过示例来掌握它的用法. 转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_02.html ByteArrayInputStream 介绍 ByteArrayInputStream 是字节数组输入流.它继承于InputStream.它包含一个内部缓冲区,该缓冲区包含从流

    Java面试准备之String类专项突破+源码分析

    String的源码中有很多Arrays的方法使用,建议先参阅Arrays的类库 基本介绍: String是一个比较特殊的类,有很多种建立的方法. 如果使用传统的构造方法比如 String s = new String("字符串");这时的对象会在堆上分配,这时候比较两个字符串地址就不相等,而用""双引号包起来的内容会在常量池中做停留,这时如果有两个内容一样的地址就一样了. 因此,使用==来比较字符串是不靠谱的. String类还实现了三个接口:Serializabl

    Java I/O系列(二)ByteArrayInputStream源码分析及理解

    定义 继承了InputStream,数据源是内置的byte数组buf,那read ()方法的使命(读取一个个字节出来),在ByteArrayInputStream就是简单的通过定向的取buf元素实现的 核心源码理解 源码: 1 public ByteArrayInputStream(byte buf[], int offset, int length) { 2 this.buf = buf; 3 this.pos = offset; 4 this.count = Math.min(offset

    Java 序列化和反序列化(二)Serializable 源码分析 - 1

    目录 Java 序列化和反序列化(二)Serializable 源码分析 - 1 1. Java 序列化接口 Java 序列化和反序列化(二)Serializable 源码分析 - 1 在上一篇文章中讲解了一下 Serializable 的大致用法,本节重点关注 Java 序列化的实现,围绕 ObjectOutputStream#writeObject 方法展开. 1. Java 序列化接口 Java 为了方便开发人员将 Java 对象进行序列化及反序列化提供了一套方便的 API 来支持.其中包

    Java 序列化和反序列化(三)Serializable 源码分析 - 2

    目录 Java 序列化和反序列化(三)Serializable 源码分析 - 2 1. ObjectStreamField 1.1 数据结构 1.2 构造函数 2. ObjectStreamClass Java 序列化和反序列化(三)Serializable 源码分析 - 2 在上一篇文章中围绕 ObjectOutputStream#writeObject 讲解了一下序列化的整个流程,这中间很多地方涉及到了 ObjectStreamClass 和 ObjectStreamField 这两个类.