声明: 本文主要以图片的形式进行说明,具体的代码可自行去文末的链接处下载。
代理模式概念:
当一个对象(代理对象)以替身或占位符的形式,来控制程序对另一个对象(源对象)的访问时,就叫代理模式
。
注:代理对象与被代理对象(即源对象)之间的交互方式有很多,常见得有:组合(代理对象持有源对象)
、继承(代理对象继承源对象)
、远程调用(代理对象远程调用源对象)
等等。
注:分类标准不同,分类结果也就不同。如,按照功效分,有保护代理
、虚拟代理
等;按照实现分,有静态代理
、动态代理(JDK动态代理、CGLIB动态代理)
;按照代理对象被代理对象之间的关系分,有组合代理
、继承代理
、远程代理
等等。
下面,介绍最常用的三种代理,以辅助理解代理模式:
静态代理:
在笔者看来,一切硬编码的代理,都属于静态代理,包括又不限于下面这种通过实现相同接口、一个子类(代理类)持有另一个子类(被代理类)的实现方式
。
- 编写静态代理:
- 测试一下:
- 编写测试类:
- 运行main方法,程序输出:
- 编写测试类:
JDK动态代理:
JDK动态代理,是利用java.lang.reflect.Proxy#newProxyInstance方法并基于(被代理类所直接实现的)接口进行的代理类的生成。Proxy#newProxyInstance内部其实是调用sun.misc.ProxyGenerator#generateProxyClass方法直接生成的字节码代理类
。所以,如果我们需要观察JDK动态代理生成的类长什么样子的话,直接使用sun.misc.ProxyGenerator#generateProxyClass输出class文件(代理类)是一种不错的选择。
注:实际上,哪怕一个类没有实现任何接口,这个类也是也可通过Proxy#newProxyInstance生成一个新的对象的,不过这个对象仅能作为一个基本的Object来使用,在试图将其强转为任何其它类(或当作某个具体类对象来使用)时,会报错,这是没有意义的;所以我们一般都是对那些有实现接口的类进行JDK动态代理。
-
使用示例:
- 第一步: 准备接口及其实现。
- 第二步: 编写InvocationHandler。
- 第三步: 使用测试。
- 编写测试类,并运行主函数:
- 观察控制台输出结果:
- 编写测试类,并运行主函数:
- 第一步: 准备接口及其实现。
-
观察生成的JDK动态代理类:
CGLIB动态代理:
JDK动态代理基于Method#invoke,为了保证通用性,Method#invoke时,传参类型是Object[],在invoke方法内部,有对参数Object[]的校验、转换等逻辑。这就使得每次调用invoke都会走一遍上述逻辑,在系统调用量很大时,这无疑是非常消耗性能的
。
CGLIB通过辅助类FastClass来进行方法的定位,在为被代理对象生成CGLIB代理对象时,会同时为这个CGLIB代理对象生成一个FastClass辅助类。生成这个辅助类的主要目的是:提供一种代理对象方法调用时,不走反射的解决方案。这是一种"笨办法",FastClass内会穷举出该代理可能会用到的所有方法,通过一个与方法签名挂钩的int值,来switch到具体的obj.xxx(...)。简单的说,FastClass内走的是对象打点调用方法,而不是Method#invoke
。
所以,JDK动态代理与CGLIB动态代理的优劣是:JDK的实现相关简单、生成动态代理类快,但是运行时效率相对较低;CGLIB的实现相对复杂、生成动态代理类略慢(至于慢多少,以现在的软硬件设备来说,其实都可以忽略不计的),但是运行时效率相对较高
。
-
使用示例:
- 第一步: 编写CGLIB的MethodInterceptor。
- 第二步: 使用测试。
- 假设我们现在有类SayHey(用于一般测试)、Other1(用于测试cglib不会代理final方法):
- 也编写了针对SayHey的测试类:
- 运行程序,控制台输出:
- 假设我们现在有类SayHey(用于一般测试)、Other1(用于测试cglib不会代理final方法):
- 第一步: 编写CGLIB的MethodInterceptor。
-
调试CGLIB,以学习CGLIB实现原理:
提示: 这里主要分享如何进行CGLIB调试,其实现原理自己去调试自己去看。总的来说,会调试了,其实现原理自然就明白了。
提示: 若少侠嫌下述步骤麻烦,那么可以直接去将本人的项目拽下来,以本人提供的几个类进行debug调试。- 概述:
- 第一步: 使用cglib本地调试模式,获取到生成的CGLIB代理类、FastClass类。
运行主函数,对应位置就有了class文件:
-
第二步: 利用反编译工具(建议直接使用IDEA),反编译class文件,然后将内容拷贝至同名的class文件(无则创建)中,若有报错则自行灵活修改一下即可。
注:java文件放哪里自行决定即可。 -
第三步(相对麻烦一点): 编写debug测试类、暴力修改器。
-
第四步: 在第二步的三个类中的对应位置打断点,debug测试类,进行调试。
- 概述:
代理模式学习完毕 !
^_^ 如有不当之处,欢迎指正
^_^ 参考资料
《Head First 设计模式》Eric Freeman & Elisabeth Freeman with Kathy Sierra & Bert Bates著,O’Reilly Taiwan公司译,UMLChina改编
^_^ 测试代码托管链接
https://github.com/JustryDeng…DesignPattern
^_^ 本文已经被收录进《程序员成长笔记》 ,笔者JustryDeng