java 内功修炼场-设计模式之代理模式

代理模式

作为官方认定的“农民工”,在受宠若惊之后唯有继续我们的学习之路。也想了一些时间,学什么呢?左思右想之后决定了回归本源,夯实我们的生存执法,首先要从内功修炼开始,内功心法开篇便是设计模式,话不多说让我们开始吧。

代理的解释

  1. 暂时代人担任 某单位的负责职务。
  2. 代理人在代理权限范围内,以被代理人的名义同第三人独立进行民事法律行为,由此产生的法律后果直接归属于被代理人的法律制度。包括法定代理、委托代理和指定代理。

以上是官方解释,用语已经相当直白,就是代替别人做某些事件,而且是暂时代替并不是永久的。

哪里可以使用代理类模式

生活中的代理模式

  • 租房中介
  • 售票黄牛
  • 婚介
  • 经纪人
  • 快递

前段时间闹的沸沸扬扬的周琦和新疆解约,签约辽宁,最终流产去往海外澳大利亚的事件,其中起着至关重要作用的周琦的CBA金牌经纪人:睢冉,如下图(右边)

image.png

                                图片来源于网络
复制代码

看过美国职业摔角 WWE 的同学们可能也会知道一个非常著名的经纪人,WWE 最凶残的选手大布的经纪人:保罗·海曼,如下图(右):

image.png

                                图片来源于网络
复制代码

这两位经纪人可以说完美的诠释了代理模式的精髓,他们不仅仅代表其当事人执行其真正的意愿,而且在此基础上搞出了很多花样,实现选手价值的同时,比如多捞些人民币或者美元,比如赚足了世界的眼球和热点,以最大利益化提高其知名度,同时也帮助自己转的盆满钵满。

日常工作中的代理模式

  • 事物代理
  • 非浸入式的日志监听
  • JDK 的动态代理
  • CGLIB 代理
  • Spring 的 AOP 动态代理
  • Spring 源码中 ProxyFactoryBean 核心方法 getObject:如果 Bean 不是单例对象,则使用代理模式活得 Bean,源码如下:

image.png

image.png

image.png

代理的目的是什么?

  • 保护目标对象:有了经纪人,球员可以专心打球,不必为了合同等琐碎的事情四处奔波。
  • 增强目标对象:伶牙俐齿的媒婆和经纪人经常为了自己和身后人的利益唇枪舌战,达到目的的同时,还要索取的更多!

代理的分类

  • 静态代理

  • 动态代理

    描述的直白一些可以简单粗暴的认为静态代理是通过代码写死的方式实现,而动态代理是根据上下文的属性通过配置的方式以通用的方式实现代理。这里的代理目的可以是执行某个方法当然包括创建对象也是属于执行某个方法。

试想一下如果 Spring 的 AOP 代理采用静态代理,会出现什么后果?下图是代理模式的 UML 类图:

image.png

图中的 ProxyImage 我们可以看出来是 RealImage 的代理类,暂且我们认为是生成图片的代理类。如果我们需要把上图做成一个通用的模块,即生成图片功能。

我们想一想,生成图片的方式不仅仅只有 ProxyImage 一种方式,我们也不可能把所有生成图片的方式统统写在一个类 ProxyImage 中。 就跟为了给儿子找对象,虽然老爸可以通过亲情朋友介绍,但是除此之外还可以通过媒婆、婚介所等很多种方式,老爸也不可能把媒婆和婚介所的所有事情都做了。

此时一个 ProxyImage 已经没法满足我们的要求,我们需要定义 ProxyImage-1、ProxyImage-2、ProxyImage-3......来逐个实现代理的功能。那我们想一想我们在看 Spring 源码的时候,又看到那么多写死的 java 类吗?显然没有。提供了一个通用的接口 InvocationHandler ,通过调用 getInstance 然后执行 invoke 来实现代理功能,以此来对我们的目标 subject(即RealImage)进行保护和功能增强。

一个 JDK 动态代理的面试题

JDK 动态代理生成对象的步骤是什么?

我们都知道 JDK 动态代理是有一个要求的,即被代理的对象必须实现了某个接口,因为如果不是通过实现接口形式,会采用 CGLIB 代理而非 JDK 动态代理,既然要求要实现接口,那当代理的时候肯定持有被代理对象的引用以及是要去获取他的所有的接口,那如何获取?这让我联想到了一个 Mybatis 的面试题:Mybatis 是如何通过 SQL 语句查到的结果集,即一张二维表转换成我们程序数值的结构化对象数据?答案读者自己琢磨,应该相当简单,实在不知可留言。所以代理对象会获取被代理对象的所有的接口。

我们常常会看到编译后的程序带美元符号开头?显然这种 .class 文件时虚拟机自动生成的,所有的密码都在这个带有 美元符号的 .class 文件中,显然这是一个新类,且时我们为了实现代理功能,对被代理对象增强和保护后的产物,之前已经获取了被代理对象的所有接口,结果已经显而易见,带有美元符号的 .class 文件里存储神秘的内容就是这些被代理对象的接口实现。具体内容就不再解释了,跟 CGLIB 代理所生成的 .class 文件内容比, JDK 的动态代理显然简单的多,起码一眼可以看明白,有兴趣的同学可以把动态代理生成的 .class 反编译一下看看里面的内容(CGLIB 代理会生成三个文件,且之间通过 FastClass 机制查找调用,而 JDK 动态代理仅仅生成一个文件)。

所有的准备工作基本完成,那下一步就更简单了,根据以上得到的内容,动态的生成java文件,然后编译成 .class 文件,最后加载到 JVM 即可。

同学们,这道面试题,大家知道如何解答了吗?依然总结不出来的同学,可以留言,有问必答!

一个 CGLIB 代理的面试题

CGLIB 代理执行代理方法的效率为什么比 JDK 的动态代理要高?

关于 CGLIB 代理的详细原理有时间令其一篇文章介绍,如果想知道的同学可留言。

在我刚刚接触 java 的时候,就有大神告诉我,反射是 java 的核心之核心,想要学好 java ,反射必须学好。这么多年,发现他说的也是对的。反射在 java 中的地位可见一斑。而在整个 JDK 动态代理中,反射更是核心之核心,可以说 JDK 的动态代理都是通过反射来获取和执行。那反射是什么?白话解释就是通过一个字符串可以实例化或者找到这个字符串表示的实例的属性、方法等。这也是一道我经常会问别人的面试题,很多人其实并不一定能迅速的答上来。我们可以再深入想一下,java 又是如何通过一个字符串实例化或者找到实例的属性或者方法呢?即 class.forName("com.xxx.xxx.xxx") 背后的神秘到底是什么?无论结论如何,有一点可以确定,即都是通过动态的方式去获得和执行某些代码,此处的重点是:动态,既然是动态那肯定比静态的更消耗资源和拉长执行时间。重点来了,也正是因为这个原因导致了 CGLIB 的代理要比 JDK 的动态代理执行效率要高,因为 CGLIB 的代理会为代理类和被代理类各自生成一个类。

假设我们被代理的类叫 TestDemo.java,代理类,通够 JDK 动态代理会会生成 美元符号+ProxyTestDemo.class文件,但是通过 CGLIB 代理会生成三个文件除了$ProxyTestDemo.class文件以外还有另外两个 .class 文件,一个文件对应TestDemo.class,另一个文件是 FastClass 类,CGLIB 代理通过 FastClass 类串联另外连个 .class 文件,于是它其实是文件之间的直接调用,是静态调用,而不是动态调用,所以 CGLIB 的代理执行代理方法效率比 JDK 的动态代理要高。

结束语

生活总有各种挑战,有正面的有负面的。有各种不实的事情扰乱视听,有各种小诱惑引导我们越走越远。未来虽然不知会如何,坚持学习总归没错,至于学什么,这需要每个人好好的思考和计划。迷茫总是暂时的,如果您总是迷茫,我们可以留言互相鼓励,抱团取暖总比孤军奋战来的容易和简单。

猜你喜欢

转载自juejin.im/post/7018206449222287391