动态代理详解

代理 是常用的基本设计模式之一,在某些情况下,一个客户不想或者不能直接引用一个对 象,此时可以通过一个称之为“代理”的第三者来实现 间接引用。代理对象可以在客户端和目标对象之间起到 中介的作用,并且可以通过代理对象去掉客户不能看到 的内容和服务或者添加客户需要的额外服务。 通过引入一个新的对象(如小图片和远程代理 对象)来实现对真实对象的操作或者将新的对象作为真 实对象的一个替身,这种实现机制即为 代理模式

简单代理

具体实现:给某一个对象提供一个代 理,并由代理对象控制对原对象的引用。 下面来看一个代理结构的简单示例:

import java.io.Serializable;

interface Interface {
  void doSomething();
  void somethingElse(String arg);
}
//被代理类
class RealObject implements Interface, Serializable {
  public void doSomething() { System.out.println("doSomething"); }
  public void somethingElse(String arg) {
    System.out.println("somethingElse " + arg);
  }
}    
//代理类
class SimpleProxy implements Interface {
  private Interface proxied;
  public SimpleProxy(Interface proxied) {
    this.proxied = proxied;
  }
  public void doSomething() {
    System.out.println("SimpleProxy doSomething");
    proxied.doSomething();
  }
  public void somethingElse(String arg) {
    System.out.println("SimpleProxy somethingElse " + arg);
    proxied.somethingElse(arg);
  }
}    

class SimpleProxyDemo {
  public static void consumer(Interface iface) {
    iface.doSomething();
    iface.somethingElse("bonobo");
  }
  public static void main(String[] args) {
    consumer(new RealObject());
    consumer(new SimpleProxy(new RealObject()));
  }
} 

Output: doSomething somethingElse bonobo SimpleProxy doSomething doSomething SimpleProxy somethingElse bonobo somethingElse bonobo

因为consumer()接受的是Interface,所以它无法知道正在获得的到底是RealObject还是SimpleProxy,因为这两者都实现了Interface。但是SimpleProxy已经被插入到了客户端和RealObject之间,因此它会执行操作,然后调用RealObject上相同的方法。

在任何时刻,只要你想要将额外的操作从”实际”对象中分离到不同的地方,特别是当你希望能够很容易的做出修改,从没有使用额外操作转为使用这些操作时,代理就会显得非常有用。例如,如果你希望跟踪对RealObject中的方法的调用,或者希望度量这些调用的开销,那么你应该怎么做呢?这时,使用代理就可以很容易地添加或移除它们。

动态代理

Java 的 动态代理 比代理的思想更向前迈进的一步,因为它可以动态的创建代理并动态的处理对所代理方法的调用。下面是从修改后的SimpleProxyDemo.java

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Interface {
  void doSomething();
  void somethingElse(String arg);
}

class RealObject implements Interface {
  public void doSomething() { System.out.println("doSomething"); }
  public void somethingElse(String arg) {
      System.out.println("somethingElse " + arg);
  }
}
class DynamicProxyHandler implements InvocationHandler {
    private Object proxied;

    public DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }

    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.print("**** proxy: " + proxy.getClass() + ", method: "
                + method + ", args: " );
        if (args != null)
            for (Object arg : args)
                System.out.print(" " + arg);
        else
            System.out.print("null");
        System.out.println();
        return method.invoke(proxied, args);
    }
}

class SimpleDynamicProxy {
    public static void consumer(Interface iface) {
        iface.doSomething();
        iface.somethingElse("bonobo");
    }

    public static void main(String[] args) {
        RealObject real = new RealObject();
        // consumer(real);
        // Insert a proxy and call again:
        Interface proxy = (Interface) Proxy.newProxyInstance(
                Interface.class.getClassLoader(),
                new Class[] { Interface.class }, new DynamicProxyHandler(real));
        consumer(proxy);
    }
}

Output: **** proxy: class $Proxy0, method: public abstract void Interface.doSomething(), args: null doSomething **** proxy: class Proxy0, method: public abstract void Interface.somethingElse(java.lang.String), args: bonobo somethingElse bonobo

通过调用静态方法Proxy.newProxyInstance()可以创建动态的创建代理类对象,这个方法需要得到一个类加载器(你通常可以从已经被加载的对象中获取其类加载器,然后传递给它),一个你希望实现的接口列表(不是类或抽象类),以及InvocationHandler接口的一个实现。 动态代理可以将所有的调用重定向到调用处理器上,因此会向调用处理器的构造器传递一个“实际”对象的引用,从而使得调用处理器在执行其中介任务时,可以请求转发。

动态代理示意图

从上图我们可以看到,代理类Proxy调用的所有方法都会转发到实现了InvocationHandler接口的invoke方法。Proxy不管客户端的业务方法是怎么实现的。当客户端调用Proxy时,它只会调用InvocationHandlerinvoke接口,所以我们的真正实现的方法就必须在invoke方法中去调用。

调用处理器中实现的invoke()方法中传递进来了代理对象proxy,可以通过此参数来区分请求的来源。method既是实际要调用的被代理类的方法,args为被调用方法所需要的参数。

动态代理之代理对象

那么proxy到底是一个什么样的对象呢,这个类到底是长什么样子呢?好的。我们再写二个方法去把这个类打印出来看个究竟,是什么三头六臂呢?我们在main下面写如下两个静态方法。

    public static void printClassDefinition(Class clz) {

        StringBuilder clzModifier = new StringBuilder();
        int mod = clz.getModifiers() & Modifier.methodModifiers();
        if (mod != 0) {
            clzModifier.append(Modifier.toString(mod)).append(' ');
        }
        String superClz = clz.getSuperclass().getName();
        if (superClz != null && !superClz.equals("")) {
            superClz = "extends " + superClz;
        }

        Class[] interfaces = clz.getInterfaces();

        String inters = "";
        for (int i = 0; i < interfaces.length; i++) {
            if (i == 0) {
                inters += "implements ";
            }
            inters += interfaces[i].getName();
        }

        System.out.println(clzModifier + clz.getName() + " " + superClz + " "
                + inters);
        System.out.println("{");

        Field[] fields = clz.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            System.out.println("\t" + fields[i].toString() + ';');
        }

        System.out.println();
        Constructor[] constructors = clz.getDeclaredConstructors();
        for (int i = 0; i < constructors.length; i++) {
            System.out.println("\t" + constructors[i].toString() + ';');
        }
        System.out.println();
        Method[] methods = clz.getDeclaredMethods();
        for (int i = 0; i < methods.length; i++) {
            System.out.println("\t" + methods[i].toString() + ';');
        }
        System.out.println("}");
    }

再改写main方法:

    public static void main(String[] args) {
        RealObject real = new RealObject();
        // consumer(real);
        // Insert a proxy and call again:
        Interface proxy = (Interface) Proxy.newProxyInstance(
                Interface.class.getClassLoader(),
                new Class[] { Interface.class }, new DynamicProxyHandler(real));
        System.out.println(proxy.getClass().getName());
        //打印出proxy类的结构
        printClassDefinition(proxy.getClass());
    }

现在重新执行main方法,我们再看看输出结果:

Output:
$Proxy0
public final $Proxy0 extends java.lang.reflect.Proxy implements Interface
{
    private static java.lang.reflect.Method $Proxy0.m1;
    private static java.lang.reflect.Method $Proxy0.m0;
    private static java.lang.reflect.Method $Proxy0.m3;
    private static java.lang.reflect.Method $Proxy0.m4;
    private static java.lang.reflect.Method $Proxy0.m2;

    public $Proxy0(java.lang.reflect.InvocationHandler);

    public final boolean $Proxy0.equals(java.lang.Object);
    public final java.lang.String $Proxy0.toString();
    public final int $Proxy0.hashCode();
    public final void $Proxy0.doSomething();
    public final void $Proxy0.somethingElse(java.lang.String);
}

此时,我们就可以看到Proxy对象的类结构,那么很明显,Proxy.newProxyInstance(Interface.class.getClassLoader(),new Class[] { Interface.class }, new DynamicProxyHandler(real))方法会做如下几件事:

  1. 根据传入的第二个参数 interfaces 动态生成一个类,实现 interfaces 中的接口,该例中即 Interface 接口的 somethingElsedoSomething 方法。并且继承了 Proxy 类,重写了 hashcode,toString,equals 等三个方法。具体实现可参看 ProxyGenerator.generateProxyClass(...); 该例中生成了$Proxy0类。
  2. 通过传入的第一个参数classloder将刚生成的类加载到jvm中。即将$Proxy0类load。
  3. 利用第三个参数,调用$Proxy0$Proxy0(InvocationHandler)构造函数 创建$Proxy0的对象,并且用interfaces参数遍历其所有接口的方法,并生成Method对象初始化对象的几个Method成员变量
  4. $Proxy0的实例返回给客户端。

现在好了。我们再看客户端怎么调就清楚了。

  1. 客户端拿到的是$Proxy0的实例对象,由于$Proxy0实现了Interface,因此转化为Interface没任何问题。 Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class[] { Interface.class }, new DynamicProxyHandler(real));
  2. proxy.doSomething() 实际上调用的是$Proxy0.doSomething(), 那么$Proxy0.doSomething()的实现就是通过InvocationHandler去调用invoke方法啦!

参考文章: 深入理解Java Proxy机制

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Coding迪斯尼

reactjs开发自制编程语言编译器:实现变量绑定和函数调用

1053
来自专栏华章科技

Python 3 入门,看这篇就够了

链接:https://shockerli.net/post/python-study-note/

3756
来自专栏大前端_Web

javascript事件监听中传递匿名函数(嵌套定义的命名函数)与命名函数的区别

版权声明:本文为吴孔云博客原创文章,转载请注明出处并带上链接,谢谢。 https://blog.csdn.net/wkyseo/articl...

1154
来自专栏项勇

笔记76 | Java中break、continue与return的区别

1514
来自专栏企鹅号快讯

Python中的while循环

原创第13篇~while循环 阅读本文大概15分钟。 文章‍结构: while定义 普通while练习 while和input函数 while 和 else w...

3746
来自专栏前端迷

在循环内使用闭包(Closures)

闭包的本质是一个内部函数访问其作用域之外的变量。闭包可以用于实现诸如 私有变量 和 创建工厂函数之类的东西。

922
来自专栏python3

python 字符编码与转换

比如一款游戏《大话西游》用的是gbk编码开发的。出口到欧美国家,是无法直接运行的。

1172
来自专栏数据结构与算法

P2085 最小函数值(minval)

题目描述 有n个函数,分别为F1,F2,...,Fn。定义Fi(x)=Aix^2+Bix+Ci (x∈N*)。给定这些Ai、Bi和Ci,请求出所有函数的所有函数...

3245
来自专栏我的博客

加强版正则表达式

以前觉得正则表达很难,今天又加强了一下正则表达的学习收获挺大的 在书写正则表达式的过程中我们遇到数字就用 最近在学习原生js,所有例子都在js下测试 常见案例一...

3969
来自专栏Golang语言社区

Golang语言--指针

在Go中指针是很容易学习的。一些进入编程任务,指针更容易操作,如通过引用调用,需要要使用指针来执行。所以学习指针成为完美Go程序员很有必要。让我们开始学习指针的...

36612

扫码关注云+社区

领取腾讯云代金券