内功心法专题-设计模式-7、代理模式
1. 结构型模式⭐️🔴
结构型模式用于描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。
结构型模式分为以下 7 种:
- 代理模式(Proxy)
- 适配器模式(Adapter)
- 装饰者模式(Decorator)
- 桥接模式(Bridge)
- 外观模式(Facade)
- 组合模式(Composite)
- 享元模式(Flyweight)
❕
2. 代理模式
2.1. 场景概述
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
原因包括:需要安全保护的对象;需要功能增强的对象
ps:功能增强也可以使用装饰者模式,可以动态的插拔能力
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
2.2. 结构
代理(Proxy)模式分为三种角色:
- 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
3. 静态代理
3.1. 实现方法
【例】火车站卖票
如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的==代理模式,火车==站是目标对象,代售点是代理对象。类图如下:
代码如下:
1 |
|
从上面代码中可以看出测试类直接访问的是ProxyPoint类对象,也就是说ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强(代理点收取一些服务费用)。
3.2. 静态代理优缺点⭐️🔴
优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展
缺点:
- 必须基于接口
- 一旦接口增加方法,目标对象与代理对象都要维护;
- 基于单一职责原则和接口隔离原则,一个代理类最好固定代理某一类接口。如果需要代理其他接口,就必须重新编写一个代理类 ❕
4. JDK动态代理
4.1. 代理过程
[[02_尚硅谷大数据技术之Spring(老师原版).docx]]
JAVA大数据集合:
https://www.bilibili.com/video/av58225341?p=261
由代理对象决定是否、何时 将方法调用转到原始对象上
4.2. 使用方法
资料来源:基本原理-2、多态与代理
示例代码:spring02: [[ArithmeticCalculatorProxy.java]]
[[pages/002-schdule/001-Arch/001-Subject/013-DemoCode/design_patterns/src/main/java/com/itheima/pattern/proxy/jdk_proxy/ProxyFactory.java]]
4.2.1. 生成代理对象⭐️🔴
有2种方式:
4.2.1.1. 先生成代理类的Class对象
Proxy.getProxyClass(loader, interfaces)
获取到Class后再获取Constructor,然后通过constructor.newInstance
获取代理对象
1 |
|
4.2.1.2. 直接生成代理对象
维护一个目标对象 , Object,在Client端设置到ProxyFactory的构造参数中
1 |
|
4.2.2. 编写代理逻辑⭐️🔴
new InvocationHandler()匿名内部类中的invoke方法
4.3. 代理类查看方法
4.3.1. 保存代理类
1 |
|
缺点:还需要使用反编译工具进行反编译
4.3.2. 使用工具类
通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)
1 |
|
可以直接copy出来保存❕
4.4. 代理类作用原理⭐️🔴⭐️🔴
^8s8bbj
代理类$Proxy00
样例:[[$Proxy00.java]]
基于接口的动态代理中,因为代理类与被代理类实现了同一个接口,所以构成了多态的形式
那么在动态的运行过程中,调用代理类的方法是如何转移到被代理类上去的呢?
- 通过InvocationHandler进行传递
- 通过代理类$Proxy00中的静态代码块中的信息进行进行关联
- 通过多态机制找到最终的被代理类 ❕
4.4.1. InvocationHandler(super.h)
❕
Proxy.newProxyInstance构造参数中的匿名内部类new InvocationHandler()
传递给了代理类的有参构造方法$Proxy00(InvocationHandler arg0)
然后又传给了Proxy的有参构造方法super(arg0)
最终传给了Proxy的私有成员变量protected InvocationHandler h
所以动态代理类中super.h.invoke
最终就是调用的我们自己定义的匿名内部类new InvocationHandler()
中的方法
❕
4.4.2. 代理类中方法判断
❕❕ ^qrabdh
静态代码块中指定了所调用方法真正属于的类型为 com.atguigu.spring.aop.proxy.ArithmeticCalculator
,这一步完成了关联 ❕
4.4.3. method.invoke
http://rui0.cn/archives/1112
https://binshao.site/2019/04/03/Reflection/#%E5%8F%8D%E5%B0%84%E8%B0%83%E7%94%A8%E7%9A%84%E6%B5%81%E7%A8%8B
4.5. 代理类生成原理
#todo
addProxyMethod https://www.cnblogs.com/Xianhuii/p/16918191.html https://blog.csdn.net/qq_43259865/article/details/113944901 https://www.cnblogs.com/Xianhuii/p/16918191.html https://gentryhuang.com/posts/d38b32e5/index.html
https://www.jianshu.com/p/471c80a7e831
4.6. 为什么要用接口⭐️🔴
通过父类Proxy
的构造方法,保存了创建代理对象过程中传进来的InvocationHandler
的实例,使用protected
修饰保证了它可以在子类中被访问和使用。但是同时,因为java是单继承的,因此代理类$Proxy00
在继承了Proxy
后,只能通过实现目标接口的方式来实现方法的扩展,达到我们增强目标方法逻辑的目的。
https://www.cnblogs.com/trunks2008/p/15930642.html
5. CGlib动态代理
https://juejin.cn/post/7026846580426833956#heading-15
https://puppylpg.github.io/2020/08/02/java-reflection-dynamic-proxy/#%E8%83%BD%E5%8A%9B%E9%97%AE%E9%A2%98
5.1. 基本介绍
● 1)静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理——这就是 Cglib 代理
● 2)Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将 Cglib 代理归属到动态代理。
● 3)Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口。它广泛的被许多 AOP 的框架使用,例如 Spring AOP,实现方法拦截
● 4)在 AOP 编程中如何选择代理模式:
○ 目标对象需要实现接口,用 JDK 代理
○ 目标对象不需要实现接口,用 Cglib 代理
● 5)Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类
❕
5.2. 使用方法
5.2.1. 引入jar包
1 |
|
5.2.2. 示例代码
1 |
|
6. 三种代理的对比⭐️🔴
jdk代理和CGLIB代理
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。
在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。
动态代理和静态代理
- 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
- 如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题
- 动态代理可以代理目标类的所有接口中的方法,而静态代理,最好一个代理负责一个接口,否则很可能违背单一职责原则和接口隔离原则 ❕
1155-🏡⭐️◼️Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {xxx})中的第二个参数是目标类所实现的所有接口,意义是一个代理类可以代理目标类所有接口中的所有方法。代理类与目标类实现了相同的接口,起点是在这里配置的◼️⭐️-point-202301261155%%
如果目标类实现多个接口,那么代理类也会实现多个接口
7. 优缺点⭐️🔴
优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
缺点:
- 增加了系统的复杂度;
8. 实战经验
9. 参考与感谢
9.1. 黑马程序员
https://www.bilibili.com/video/BV1Np4y1z7BU?p=35
课件已下载:/Volumes/Seagate Bas/001-ArchitectureRoad/资料-java设计模式(图解+框架源码分析+实战)
示例代码:/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/013-DemoCode/design_patterns
[[pages/002-schdule/001-Arch/001-Subject/009-内功心法专题/设计模式/黑马/设计模式]]
9.2. 尚硅谷
https://www.bilibili.com/video/BV1Dx411f7ib/?p=266&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204
示例代码:/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/013-DemoCode/Spring02