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

动态代理概念及类图

上一篇中介绍了静态代理,动态代理跟静态代理一个最大的差别就是:动态代理是在执行时刻动态的创建出代理类及其对象。

上篇中的静态代理是在编译的时候就确定了代理类详细类型。假设有多个类须要代理。那么就得创建多个。

另一点,假设Subject中新增了一个方法,那么相应的实现接口的类中也要相应的实现这些方法。

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

类图例如以下所看到的:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXVqaW43NTM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

上面类图中使用的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 映射表中获取以类装载器对象为keyword所相应的缓存表。假设不存在就创建一个新的缓存表并更新到 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/

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

    1. (转)轻松学,Java 中的代理模式及动态代理

      背景:讲到反射机制,肯定会想到动态代理. 轻松学,Java 中的代理模式及动态代理 代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强.值得注意的是,代理类和被代理类应该 ...

    2. java设计模式(一)动态代理模式,JDK与CGLIB分析

      -本想着这个知识点放到Spring Aop说说可能更合适一点,但因为上一篇有所提到就简单分析下,不足之处请多多评论留言,相互学习,有所提高才是关键! 什么是代理模式: 记得有本24种设计模式的书讲到代 ...

    3. 动态代理模式——JDK动态代理

      今天,我就来讲一下动态代理的设计模式. 动态代理的意义在于生成一个代理对象,来代理真实对象,从而控制真实对象的访问.操作动态代理需要两个步骤:一.代理对象和真实对象建立代理关系.二.实现代理对象的代理 ...

    4. Java的三种代理模式(Spring动态代理对象)

      Java的三种代理模式 1.代理模式 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩 ...

    5. JAVA代理模式与动态代理模式

      1.代理模式 所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动.在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用.代理模式给某 ...

    6. java代理模式及动态代理类

       1.      代理模式 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用 ...

    7. Java代理模式之动态代理

      动态代理类的源码是程序在运行期间由JVM根据反射等机制动态生成的,所以不存在代理类的字节码文件.代理角色和真实角色的联系在程序运行时确定! Java中有两种动态代理,一种是JDK自带的,另一种的CGL ...

    8. 代理模式-jdk动态代理

      IDB package com.bjpowernode.proxy; /** * 代理类和目标类都必须使用同一个接口. */ public interface IDB { int insert(); ...

    9. 代理模式与动态代理之JDK实现和CGlib实现

      静态代理 静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类. 由业务实现类.业务代理类 两部分组成.业务实现类 负责实现主要的业务方法,业务代理类负责对调用的业务方法作拦截.过滤.预处理, ...

    随机推荐

    1. 27、Django实战第27天:全局搜索功能开发

      当我们选择其中一个类别(公开课,课程讲师,授课老师),输入搜索内容,点击搜索后会跳转到相应的列表页中进行展示 我们输入的内容作为参数keyword传入后台 搜索"公开课" 当课程中 ...

    2. 加快Vue项目的开发速度

      巧用Webpack Webpack是实现我们前端项目工程化的基础,但其实她的用处远不仅仅如此,我们可以通过Webpack来帮我们做一些自动化的事情.首先我们要了解require.context()这个 ...

    3. PHP Shell生成工具Weevely

      PHP Shell生成工具Weevely   Weevely是一款模拟Telnet连接的PHP Shell工具.它不提供网页形式的接口,而是提供一个命令形式的终端.渗透测试人员首先使用该工具生成对应的 ...

    4. Largest Divisible Subset -- LeetCode

      Given a set of distinct positive integers, find the largest subset such that every pair (Si, Sj) of ...

    5. luogu P1126 机器人搬重物

      题目描述 机器人移动学会(RMI)现在正尝试用机器人搬运物品.机器人的形状是一个直径1.6米的球.在试验阶段,机器人被用于在一个储藏室中搬运货物.储藏室是一个N*M的网格,有些格子为不可移动的障碍.机 ...

    6. CodeForces - 986C AND Graph

      不难想到,x有边连出的一定是 (2^n-1) ^ x 的一个子集,直接连子集复杂度是爆炸的...但是我们可以一个1一个1的消去,最后变成补集的一个子集. 但是必须当且仅当 至少有一个 a 等于 x 的 ...

    7. HDOJ 4961 Boring Sum

      Discription Number theory is interesting, while this problem is boring. Here is the problem. Given a ...

    8. Djanog|requirements.txt生成

      Django | requirement.txt 生成 pip django 1   pip 通常我们熟悉使用的都是 pip, 这个工具确实方便项目管理依赖包.当想把当前项目依赖的包的名称和版本导入指 ...

    9. [JOISC2014]JOIOJI

      题目大意: 给你一串仅包含'J''O''I'的字符串,问满足三种字符出现次数相等的最大字串是多少? 思路: 用map存一下出现次数前缀和两两之差出现的最早位置,每次看一下当前的两两之差最早的出现位置是 ...

    10. ArcMap绘图时,节点显示时的小数点位数

      直接来图吧,省的啰嗦了: 打开选中节点的,节点坐标列表(Edit Sketch Properties):