专栏名称: 老马说编程
从入门到高级, 深入浅出, 老马和你一起探索编程及计算机技术的本质, 篇篇原创, 用心写作。
目录
相关文章推荐
老刘说NLP  ·  再看知识图谱本体生成:RAG用于Mysql数 ... ·  昨天  
程序员的那些事  ·  不到 2 个月,OpenAI 火速用 ... ·  2 天前  
程序员小灰  ·  你的技术栈,还能撑几年? ·  2 天前  
老刘说NLP  ·  RAG&KG&LLM&文档智能四大领域技术前 ... ·  4 天前  
51好读  ›  专栏  ›  老马说编程

(86) 动态代理 / 计算机程序的思维逻辑

老马说编程  · 公众号  · 程序员  · 2017-05-04 23:14

正文

请到「今天看啥」查看全文



代码看起来更为复杂了,这有什么用呢?别着急,我们慢慢解释。IService和RealService的定义不变,程序的输出也没变,但代理对象proxyService的创建方式变了,它使用java.lang.reflect包中的Proxy类的静态方法newProxyInstance来创建代理对象,这个方法的声明如下:

public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)


它有三个参数:

  • loader表示类加载器,下节我们会单独探讨它,例子使用和IService一样的类加载器

  • interfaces表示代理类要实现的接口列表,是一个数组, 元素的类型只能是接口,不能是普通的类 ,例子中只有一个IService

  • h的类型为InvocationHandler,它是一个接口,也定义在java.lang.reflect包中,它只定义了一个方法invoke,对代理接口所有方法的调用都会转给该方法


newProxyInstance的返回值类型为Object,可以强制转换为interfaces数组中的某个接口类型,这里我们强制转换为了IService类型,需要注意的是, 它不能强制转换为某个类类型 ,比如RealService,即使它实际代理的对象类型为RealService。

SimpleInvocationHandler实现了InvocationHandler,它的构造方法接受一个参数realObj表示被代理的对象,invoke方法处理所有的接口调用,它有三个参数:

  • proxy表示代理对象本身,需要注意,它不是被代理的对象,这个参数一般用处不大

  • method表示正在被调用的方法

  • args表示方法的参数


在SimpleInvocationHandler的invoke实现中,我们调用了method的invoke方法,传递了实际对象realObj作为参数,达到了调用实际对象对应方法的目的,在调用任何方法前后,我们输出了跟踪调试语句。需要注意的是, 不能将proxy作为参数传递给method.invoke ,比如:

Object result = method.invoke(proxy, args);


上面的语句会出现死循环,因为proxy表示当前代理对象,这么调用又会调用到SimpleInvocationHandler的invoke方法。

基本原理
看了上面的介绍是不是更晕了,没关系,看下Proxy.newProxyInstance的内部就理解了。上面例子中创建proxyService的代码可以用如下代码代替:

Class> proxyCls = Proxy.getProxyClass(IService.class.getClassLoader(),
new Class>[] { IService.class });
Constructor> ctor = proxyCls.getConstructor(new Class>[] { InvocationHandler.class });
InvocationHandler handler = new SimpleInvocationHandler(realService);
IService proxyService = (IService) ctor.newInstance(handler);


分为三步:

  1. 通过Proxy.getProxyClass创建代理类定义,类定义会被缓存

  2. 获取代理类的构造方法,构造方法有一个InvocationHandler类型的参数

  3. 创建InvocationHandler对象,创建代理类对象


Proxy.getProxyClass需要两个参数,一个是ClassLoader,另一个是接口数组,它会动态生成一个类,类名以$Proxy开头,后跟一个数字,对于上面的例子,动态生成的类定义如下所示,为简化起见,我们忽略了异常处理的代码:

final class $Proxy0 extends Proxy implements SimpleJDKDynamicProxyDemo.IService {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;

public $Proxy0(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}

public final boolean equals(Object paramObject) {
return ((Boolean) this.h.invoke(this, m1,
new Object[] { paramObject })).booleanValue();
}

public final void sayHello() {
this.h.invoke(this, m3, null);
}

public final String toString() {
return (String) this.h.invoke(this, m2, null);
}

public final int hashCode() {
return ((Integer) this.h.invoke(this, m0, null)).intValue();
}

static {
m1 = Class.forName("java.lang.Object").getMethod("equals",
new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("laoma.demo.proxy.SimpleJDKDynamicProxyDemo$IService")
.getMethod("sayHello",new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
}
}


$Proxy0的父类是Proxy,它有一个构造方法,接受一个InvocationHandler类型的参数,保存为了实例变量h,h定义在父类Proxy中,它实现了接口IService,对于每个方法,如sayHello,它调用InvocationHandler的invoke方法,对于Object中的方法,如hashCode, equals和toString, $Proxy0同样转发给了InvocationHandler。

可以看出, 这个类定义本身与被代理的对象没有关系,与InvocationHandler的具体实现也没有关系,而主要与接口数组有关,给定这个接口数组,它动态创建每个接口的实现代码,实现就是转发给InvocationHandler,与被代理对象的关系以及对它的调用由InvocationHandler的实现管理

我们是怎么知道$Proxy0的定义的呢?对于Oracle的JVM,可以配置java的一个属性得到,比如:

java -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true shuo.laoma.dynamic.c86.SimpleJDKDynamicProxyDemo


以上命令会把动态生成的代理类$Proxy0保存到文件$Proxy0.class中,通过一些反编译器工具比如JD-GUI(http://jd.benow.ca/)就可以得到源码。

理解了代理类的定义,后面的代码就比较容易理解了,就是获取构造方法,创建代理对象。

动态代理的优点
相比静态代理,动态代理看起来麻烦了很多,它有什么好处呢? 使用它,可以编写通用的代理逻辑,用于各种类型的被代理对象,而不需要为每个被代理的类型都创建一个静态代理类







请到「今天看啥」查看全文