Java 技术之动态代理机制
文章推荐
- 精选java等全套学习资源
- 精选java电子图书资源
- 精选大数据学习资源
- java项目练习精选
###静态代理
常规的代理模式有以下三个部分组成:
功能接口
interface IFunction {void doAThing();
}
功能提供者
class FunctionProvider implement IFunction {public void doAThing {System.out.print("do A");}
}
功能代理者
class Proxy implement IFunction {private FunctionProvider provider;Proxy(FunctionProvider provider) {this.provider = provider;}public void doAThing {provider.doAThing();}
}
前两者就是普通的接口和实现类,而第三个就是所谓的代理类。对于使用者而言,他会让代理类去完成某件任务,并不关心这件任务具体的跑腿者。
这就是静态代理,好处是方便调整变换具体实现类,而使用者不会受到任何影响。
不过这种方式也存在弊端:比如有多个接口需要进行代理,那么就要为每一个功能提供者创建对应的一个代理类,那就会越来越庞大。而且,所谓的“静态”代理,意味着必须提前知道被代理的委托类。
通过下面一个例子来说明下:
统计函数耗时–静态代理实现
现在希望通过一个代理类,对我感兴趣的方法进行耗时统计,利用静态代理有如下实现:
interface IAFunc {void doA();
}
interface IBFunc {void doB();
}class TimeConsumeProxy implement IAFunc, IBFunc {private AFunc a;private BFunc b;public(AFunc a, BFunc b) {this.a = a;this.b = b;}void doA() {long start = System.currentMillions();a.doA();System.out.println("耗时:" + (System.currentMillions() - start));}void doB() {long start = System.currentMillions();b.doB();System.out.println("耗时:" + (System.currentMillions() - start));}
}
弊端很明显,如果接口越多,每新增一个函数都要去修改这个 TimeConsumeProxy 代理类:把委托类对象传进去,实现接口,在函数执行前后统计耗时。
这种方式显然不是可持续性的,下面来看下使用动态代理的实现方式,进行对比。
###动态代理
动态代理的核心思想是通过 Java Proxy 类,为传入进来的任意对象动态生成一个代理对象,这个代理对象默认实现了原始对象的所有接口。
还是通过统计函数耗时例子来说明更加直接。
统计函数耗时–动态代理实现
interface IAFunc {void doA();
}
interface IBFunc {void doB();
}class A implement IAFunc { ... }
class B implement IBFunc { ... }class TimeConsumeProxy implements InvocationHandler {private Object realObject;public Object bind(Object realObject) {this.realObject = realObject;Object proxyObject = Proxy.newInstance(realObject.getClass().getClassLoader(),realObject.getClass().getInterfaces(),this);return proxyObject;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {long start = System.currentMillions();Object result = method.invoke(target, args);System.out.println("耗时:" + (System.currentMillions() - start));return result;}
}
具体使用时:
public static void main(String[] args) {A a = new A();IAFunc aProxy = (IAFunc) new TimeConsumeProxy().bind(a);aProxy.doA();B b = new B();IBFunc bProxy = (IBFunc) new TimeConsumeProxy().bind(b);bProxy.doB();
}
这里最大的区别就是:代理类和委托类 互相透明独立,逻辑没有任何耦合,在运行时才绑定在一起。这也就是静态代理与动态代理最大的不同,带来的好处就是:无论委托类有多少个,代理类不受到任何影响,而且在编译时无需知道具体委托类。
回到动态代理本身,上面代码中最重要的就是:
Object proxyObject = Proxy.newInstance(realObject.getClass().getClassLoader(),realObject.getClass().getInterfaces(),this);
通过 Proxy 工具,把真实委托类转换成了一个代理类,最开始提到了一个代理模式的三要素:功能接口、功能提供者、功能代理者;在这里对应的就是:realObject.getClass().getInterfaces()
,realObject
,TimeConsumeProxy
。
其实动态代理并不复杂,通过一个 Proxy 工具,为委托类的接口自动生成一个代理对象,后续的函数调用都通过这个代理对象进行发起,最终会执行到 InvocationHandler#invoke
方法,在这个方法里除了调用真实委托类对应的方法,还可以做一些其他自定义的逻辑,比如上面的运行耗时统计等。
###探索动态代理实现机制
好了,上面我们已经把动态代理的基本用法及为什么要用动态代理进行了讲解,很多文章到这里也差不多了,不过我们还准备进一步探索一下给感兴趣的读者。
抛出几个问题:
上面生成的代理对象 Object proxyObject 究竟是个什么东西?为什么它可以转型成 IAFunc,还能调用doA() 方法?
这个 proxyObject 是怎么生成出来的?它是一个 class 吗?
下面我先给出答案,再一步步探究这个答案是如何来的。
问题一: proxyObject
究竟是个什么 -> 动态生成的 $Proxy0.class
文件
在调用 Proxy.newInstance
后,Java 最终会为委托类 A 生成一个真实的 class 文件:$Proxy0.class
,而 proxyObject
就是这个 class 的一个实例。
猜一下,这个 $Proxy0.class
类长什么样呢,包含了什么方法呢?回看下刚刚的代码:
IAFunc aProxy = (IAFunc) new TimeConsumeProxy().bind(a);
aProxy.doA();
推理下,显然这个 $Proxy0.class
实现了 IAFunc 接口,同时它内部也实现了 doA()
方法,而且重点是:这个 doA()
方法在运行时会执行到 TimeConsumeProxy#invoke()
方法里。
重点来了!下面我们来看下这个 $Proxy0.class
文件,把它放进 IDE 反编译下,可以看到如下内容,来验证下刚刚的猜想:
final class $Proxy0 extends Proxy implements IAFunc {private static Method m1;private static Method m3;private static Method m2;private static Method m0;public $Proxy0(InvocationHandler var1) throws {super(var1);}public final boolean equals(Object var1) throws {// 省略}public final void doA() throws {try {// 划重点super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final String toString() throws {// 省略}public final int hashCode() throws {// 省略}static {try {// 划重点m3 = Class.forName("proxy.IAFunc").getMethod("doA", new Class[0]);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]);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());}}
}
没错,刚刚的猜想都中了!实现了 IAFunc 接口和 doA() 方法,不过,doA()里是什么鬼?
super.h.invoke(this, m3, (Object[])null);
回看下,TimeConsumeProxy
里面的 invoke
方法,它的函数签名是啥?
public Object invoke(Object proxy, Method method, Object[] args);
没错,doA()
里做的就是调用 TimeConsumeProxy#invoke()
方法。
那么也就是说,下面这段代码执行流程如下:
IAFunc aProxy = (IAFunc) new TimeConsumeProxy().bind(a);
aProxy.doA();
基于传入的委托类 A,生成一个$Proxy0.class
文件;
创建一个 $Proxy0.class
对象,转型为 IAFunc
接口;
调用 aProxy.doA()
时,自动调用 TimeConsumeProxy
内部的 invoke
方法。
问题二:proxyObject 是怎么一步步生成出来的 -> $Proxy0.class 文件生成流程
刚刚从末尾看了结果,现在我们回到代码的起始端来看:
Object proxyObject = Proxy.newInstance(realObject.getClass().getClassLoader(),realObject.getClass().getInterfaces(),this);
准备好,开始发车读源码了。我会截取重要的代码并加上注释。
先看Proxy.newInstance()
:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) {// 复制要代理的接口 final Class<?>[] intfs = interfaces.clone();// 重点:生成 $Proxy0.class 文件并通过 ClassLoader 加载进来Class<?> cl = getProxyClass0(loader, intfs);// 对 $Proxy0.class 生成一个实例,就是 `proxyObject`final Constructor<?> cons = cl.getConstructor(constructorParams);return cons.newInstance(new Object[]{h});
}
再来看 getProxyClass0
的具体实现:ProxyClassFactory
工厂类:
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {// 参数为 ClassLoader 和要代理的接口Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);// 1. 验证 ClassLoader 和接口有效性for (Class<?> intf : interfaces) {// 验证 classLoader 正确性Class<?> interfaceClass = Class.forName(intf.getName(), false, loader);if (interfaceClass != intf) {throw new IllegalArgumentException(intf + " is not visible from class loader");}// 验证传入的接口 class 有效if (!interfaceClass.isInterface()) { ... } // 验证接口是否重复if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { ... }}// 2. 创建包名及类名 $Proxy0.classproxyPkg = ReflectUtil.PROXY_PACKAGE + ".";long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num;// 3. 创建 class 字节码内容byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);// 4. 基于字节码和类名,生成 Class<?> 对象return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
}
再看下第三步生成 class 内容 ProxyGenerator.generateProxyClass
:
// 添加 hashCode equals toString 方法
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
// 添加委托类的接口实现
for (int i = 0; i < interfaces.length; i++) {Method[] methods = interfaces[i].getMethods();for (int j = 0; j < methods.length; j++) {addProxyMethod(methods[j], interfaces[i]);}
}
// 添加构造函数
methods.add(this.generateConstructor());
这里构造好了类的内容:添加必要的函数,实现接口,构造函数等,下面就是要写入上一步看到的 $Proxy0.class
了。
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
dout.writeInt(0xCAFEBABE);
...
dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
...
return bout.toByteArray();
到这里就生成了第一步看到的 $Proxy0.class
文件了,完成闭环,讲解完成!
###动态代理小结
通过上面的讲解可以看出,动态代理可以随时为任意的委托类进行代理,并可以在 InvocationHandler#invoke
拿到运行时的信息,并可以做一些切面处理。
在动态代理背后,其实是为一个委托类动态生成了一个 $Proxy0.class
的代理类,该代理类会实现委托类的接口,并把接口调用转发到 InvocationHandler#invoke
上,最终调用到真实委托类的对应方法。
动态代理机制把委托类和代理类进行了隔离,提高了扩展性。
###Java 动态代理与 Python 装饰器
这是 Java 语言提供的一个有意思的语言特性,而其实 Python 里也提供了一种类似的特性:装饰器,可以达到类似的面相切面编程思想,下次有空再把两者做下对比,这次先到这。
转自:https://juejin.im/entry/5a82fd0f6fb9a0633757368c
文章有不当之处,欢迎指正,你也可以关注我的微信公众号:好好学java
,获取优质资源。
Java 技术之动态代理机制相关推荐
- Java进阶 | Proxy动态代理机制详解
一.Jvm加载对象 在说Java动态代理之前,还是要说一下Jvm加载对象的过程,这个依旧是理解动态代理的基础性原理: Java类即源代码程序.java类型文件,经过编译器编译之后就被转换成字节代码.c ...
- Java 动态代理机制详解
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
- Java 动态代理机制分析及扩展,第 1 部分
引言 Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执 ...
- Java 动态代理机制分析及扩展
简介: 本文通过分析 Java 动态代理的机制和特点,解读动态代理类的源代码,并且模拟推演了动态代理类的可能实现,向读者阐述了一个完整的 Java 动态代理运作过程,希望能帮助读者加深对 Java 动 ...
- 详解Java动态代理机制
之前介绍的反射和注解都是Java中的动态特性,还有即将介绍的动态代理也是Java中的一个动态特性.这些动态特性使得我们的程序很灵活.动态代理是面向AOP编程的基础.通过动态代理,我们可以在运行时动态创 ...
- [Java|面试] 面试被问Java的动态代理机制,能说说吗
Java的动态代理机制 文章目录 Java的动态代理机制 0. 什么是代理 1.动态代理和静态代理的区别 2. 使用代理的情况 3. 动态代理的构成 4. JDK中的动态代理 5. 手写一个JDK动态 ...
- java的动态代理机制详解
2019独角兽企业重金招聘Python工程师标准>>> 参考资料 1.java的动态代理机制详解 转载于:https://my.oschina.net/Howard2016/blog ...
- Java 动态代理机制分析及扩展--转
http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/#icomments http://www.ibm.com/developerworks/c ...
- 如何使用Proxy模式及Java内建的动态代理机制
http://zhangjunhd.blog.51cto.com/113473/69996 1.Proxy模式 代理模式支持将某些操作从实际的对象中分离出来,通过它的代理类提供处理.这样便于修改和管理 ...
最新文章
- 分布式文件系统—HDFS—Java API操作
- 剑指offer_06
- .net如何引用该命名空间
- RabbbitMq Return 消息机制
- Android源码和内核源码的下载,编译和执行
- 95-30-010-Channel-AbstractChannel
- 高通首次演示基于3GPP的5G新空口连接 有望成为全球标准
- c ++中哈希表如何访问_C / C ++中的哈希表–完整的实现
- Hibernate不能自动建表解决办法【转载】
- BTrace入门教程
- [AHK]双击Ctrl+C调用谷歌翻译!
- VLAN间路由的配置(一)普通VLAN间路由的配置
- 优化问题---切线、切向量、切平面;法线,法向量,法平面
- QT框架下的OpenGL使用---实战篇---鼠标选取点对象
- 一、《图解HTTP》了解Web和网络基础
- 图像在空域上的平滑处理
- 微信小程序云开发导出CSV文件时出现乱码
- java提供按摩比较复数大小_复数类实现 - kb
- 数据结构之二叉树的实现
- 由浪潮大型机M13说开去