设计模式系列 - 代理模式及动态代理详解
定义
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
结构
抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用
优缺点
优点
- 代理模式能将代理对象与真实被调用的目标对象分离。
- 一定程度上降低了系统的耦合度,扩展性好。
- 可以起到保护目标对象的作用。
- 可以对目标对象的功能增强。
缺点
- 代理模式会造成系统设计中类的数量增加。
- 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
应用实例
静态代理
业务描述:
有个哥们 想和他对象求婚,但是自己没有经验,又想浪漫一点,于是就找了一个“婚姻策划”
代码实现
package com.beauty.designpatterns.structure;/*** description 代理** @author yufengwen* @date 2023/2/28 17:45*/
public class ProxyPattern {public static void main(String[] args) {Marriage lover = new Lover();Marriage scheme = new Scheme(lover);scheme.propose();}}// 求婚策划/*** 接口*/
interface Marriage{/*** 求婚*/void propose();}/*** 具体的对象*/
class Lover implements Marriage{/*** 求婚*/@Overridepublic void propose() {System.out.println("嫁给我吧");}
}/*** 代理对象*/
class Scheme implements Marriage{private Marriage marriage;public Scheme(Marriage marriage) {this.marriage = marriage;}/*** 求婚*/@Overridepublic void propose() {System.out.println("前期策划方案");System.out.println("===================================");marriage.propose();System.out.println("===================================");System.out.println("策划后期");}
}
动态代理
JDK动态代理
JDK动态代理步骤
JDK动态代理分为以下几步:
- 拿到被代理对象的引用,并且通过反射获取到它的所有的接口。
- 通过JDK Proxy类重新生成一个新的类,同时新的类要实现被代理类所实现的所有的接口。
- 动态生成 Java 代码,把新加的业务逻辑方法由一定的逻辑代码去调用。
- 编译新生成的 Java 代码.class。
- 将新生成的Class文件重新加载到 JVM 中运行。
所以说JDK动态代理的核心是通过重写被代理对象所实现的接口中的方法来重新生成代理类来实现的,那么假如被代理对象没有实现接口呢?那么这时候就需要CGLIB动态代理了。
/*** description* 主体方法 Proxy.newProxyInstance(ClassLoader loader,* Class<?>[] interfaces,* InvocationHandler h)* InvocationHandler的实现为 代理的实现** @author yufengwen* @date 2021/12/6 2:34 下午*/
public class JdkProxy {public static void main(String[] args) {//第一步:创建被代理对象Person xm = new Xm();// 第二步:创建handler,传入真实对象InvocationHandler invocationHandler = new MyInvocationHandler(xm);//第三步:创建代理对象,传入类加载器、接口、handlerfinal Person person = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, invocationHandler);//第四步:调用方法person.eat();person.say();}
}/*** 被代理类*/
class Xm implements Person{@Overridepublic void eat() {System.out.println("我是小明,我在吃东西");}@Overridepublic void say() {System.out.println("我是小明,我在说话");}
}/**
* 代理接口
*/
interface Person{void eat();void say();
}/**
* 实现InvocationHandler;编写具体的代理逻辑
*/
class MyInvocationHandler implements InvocationHandler{private Object target;public MyInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("jdk 代理操作前逻辑");Object result = method.invoke(target, args);System.out.println("jdk 代理操作后逻辑");return result;}
}
测试结果
jdk 代理操作前逻辑
我是小明,我在吃东西
jdk 代理操作后逻辑
jdk 代理操作前逻辑
我是小明,我在说话
jdk 代理操作后逻辑Process finished with exit code 0
cglib动态代理
JDK动态代理是通过重写被代理对象实现的接口中的方法来实现,而CGLIB是通过继承被代理对象来实现,和JDK动态代理需要实现指定接口一样,CGLIB也要求代理对象必须要实现MethodInterceptor接口,并重写其唯一的方法intercept。
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。(利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理)
注意:因为CGLIB是通过继承目标类来重写其方法来实现的,故而如果是final和private方法则无法被重写,也就是无法被代理。
/*** description** @author yufengwen* @date 2021/12/6 3:00 下午*/
public class CglibProxy {public static void main(String[] args) {final CgProxy<XiaoMing> callback = new CgProxy<>();final XiaoMing proxy = callback.getProxy(XiaoMing.class);proxy.eat();}}/*** 实现 MethodInterceptor* 重写 intercept 方法 实现具体的代理逻辑* @param <T>*/
class CgProxy<T> implements MethodInterceptor{public T getProxy(Class<T> clazz){Enhancer enhancer = new Enhancer();//设置父类enhancer.setSuperclass(clazz);//设置方法拦截处理器enhancer.setCallback(this);return (T) enhancer.create();}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("cglib 代理 前逻辑");System.out.println(method.getName());methodProxy.invokeSuper(o,objects);System.out.println("cglib 代理 后逻辑");return null;}
}
测试结果:
cglib 代理 前逻辑
eat
我是小明,我在吃东西
cglib 代理 后逻辑Process finished with exit code 0
CGLIB动态代理实现分析
CGLib动态代理采用了FastClass机制,其分别为代理类和被代理类各生成一个FastClass,这个FastClass类会为代理类或被代理类的方法分配一个 index(int类型)。这个index当做一个入参,FastClass 就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比 JDK 动态代理通过反射调用更高。
但是我们看上面的源码也可以明显看到,JDK动态代理只生成一个文件,而CGLIB生成了三个文件,所以生成代理对象的过程会更复杂
JDK和CGLib动态代理对比
JDK 动态代理是实现了被代理对象所实现的接口,CGLib是继承了被代理对象。 JDK和CGLib 都是在运行期生成字节码,JDK是直接写Class字节码,CGLib 使用 ASM 框架写Class字节码,Cglib代理实现更复杂,生成代理类的效率比JDK代理低。
JDK 调用代理方法,是通过反射机制调用,CGLib 是通过FastClass机制直接调用方法,CGLib 执行效率更高。
原理区别:
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。核心是实现InvocationHandler接口,使用invoke()方法进行面向切面的处理,调用相应的通知。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。核心是实现MethodInterceptor接口,使用intercept()方法进行面向切面的处理,调用相应的通知。
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
性能区别:
1、CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
2、在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理。
各自局限:
1、JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理。
2、cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
类型 | 机制 | 回调方式 | 适用场景 | 效率 |
---|---|---|---|---|
JDK动态代理 | 委托机制,代理类和目标类都实现了同样的接口,InvocationHandler持有目标类,代理类委托InvocationHandler去调用目标类的原始方法 | 反射 | 目标类是接口类 | 效率瓶颈在反射调用稍慢 |
CGLIB动态代理 | 继承机制,代理类继承了目标类并重写了目标方法,通过回调函数MethodInterceptor调用父类方法执行原始逻辑 | 通过FastClass方法索引调用 | 非接口类、非final类,非final方法 | 第一次调用因为要生成多个Class对象,比JDK方式慢。多次调用因为有方法索引比反射快,如果方法过多,switch case过多其效率还需测试 |
静态代理和动态的本质区别
- 静态代理只能通过手动完成代理操作,如果被代理类增加新的方法,代理类需要同步新增,违背开闭原则。
- 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
- 若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。
代理模式和装饰器模式的区别
代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。
个人感觉两者最主要的区别在于:代理模式增加的代理逻辑 不是为了增强本身的功能;****装饰器模式主要就是为了增强原始类本身的逻辑
主要应用场景
代理模式常用在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类统一处理,让程序员只需要关注业务方面的开发。除此之外,代理模式还可以用在 RPC、缓存等应用场景中。
参考文章:
设计模式之美
https://juejin.cn/post/7011357346018361375
相关文章:

设计模式系列 - 代理模式及动态代理详解
定义 为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。 结构 抽象角色:通过接口或抽象类声明真实角色实现的业务方法。 代…...

【分享】订阅集简云畅捷通T+cloud连接器自动同步财务费用单至畅捷通
方案场景 伴随公司发展和数字化水平提高,大量的财务单据需要手动审核和录入,这些重复机械的操作占据大量人力,同时极容易出现数据出错或丢失等情况,严重影响着企业经营效率。 使用集简云提供服务的畅捷通TCloud钉钉连接器完成财…...
GPT的发展历程
GPT是当前最火的人工智能技术之一,自推出以来就广受关注。但大家对这个技术了解多少,又知道它经历了什么? GPT的诞生离不开谷歌在人工智能领域的努力和研究。2004年,谷歌成立了人工智能实验室(现已成为谷歌 AI实验室&…...
iOS开发笔记之九十八——关于Memory Leak总结笔记
*****阅读完此文,大概需要3分钟******关于Memory leak(内存泄漏)的问题,如果是面试被问这个问题以及此类问题,主要涉及下面3个方面:内存泄漏的常见场景有哪些,列举几个常见的例子?开…...

HTML基础语法
一 前端简介构成语言说明结构HTML页面元素和内容表现CSS网页元素的外观和位置等页面样式(美化)行为JavaScript网页模型的定义和页面交互二 HTML1.简介HTML(Hyper Text Markup Language):超文本标记语言。网页结构整体&…...

微软新版必应gpt人工智能体验教程
大家好,我是雄雄,欢迎关注微信公众号:** 雄雄的小课堂 ** 现在是:2023年2月28日18:35:02 前言 前几天,发了一篇文章,主要介绍了如何申请新必应的内测名单,其实一共也就那几步,然后等着就行: 文章连接:new bing如何快速申请内测资格,从而体验人工智能? 今天,终于…...

你问我答|虚拟机、容器和无服务器,怎么选?
在新技术层出不穷的当下,每家企业都希望不断降低成本,并提高运营效率,一个方法就是寻找不同的技术方案来优化运营。 例如,曾经一台服务器只能运行一个应用(裸机);接着,一台服务器的资源可以划分为多个块,从而运行多个应用(虚拟化);再到后来,应用越来越多,为了方便它们…...

某建筑设计研究院“综合布线管理软件”应用实践
某建筑设计研究院有限公司(简称“某院”)隶属于国务院国资委直属的大型骨干科技型中央企业。“某院”前身为中央直属设计公司,创建于1952年。成立近70年来,始终秉承优良传统,致力于推进国内勘察设计产业的创新发展&…...

R语言绘制SCI论文中常见的箱线散点图,并自动进行方差分析计算显著性水平
显著性标记箱线散点图 本篇笔记的内容是在R语言中利用ggplot2,ggsignif,ggsci,ggpubr等包制作箱线散点图,并计算指定变量之间的显著性水平,对不同分组进行特异性标记,最终效果如下。 加载R包 library(ggplo…...

redux-saga
redux-saga 官网:About | Redux-Saga 中文网:自述 Redux-Saga redux-saga 是一个用于管理 异步获取数据(副作用) 的redux中间件;它的目标是让副作用管理更容易,执行更高效,测试更简单,处理故障时更容易… …...

【C++】-- 智能指针
目录 智能指针意义 智能指针的使用及原理 RAII 智能指针的原理 std::auto_ptr std::auto_ptr的模拟实现 std::unique_ptr std::unique_ptr模拟实现 std::shared_ptr std::shared_ptr的模拟实现 循环引用问题 智能指针意义 #问:为什么需要智能指针&#…...

数据结构与算法——4时间复杂度分析2(常见的大O阶)
这篇文章是时间复杂度分析的第二篇。在前一篇文章中,我们从0推导出了为什么要用时间复杂度,时间复杂度如何分析以及时间复杂度的表示三部分内容。这篇文章,是对一些常用的时间复杂度进行一个总结,相当于是一个小结论 1.常见的大O…...
IIS解析漏洞
IIS 6.0在解析文件时存在以下两个解析漏洞。 ①当建立*.asa、*.asp格式的文件夹时,其目录下的任意文件都将被IIS当作asp文件来解析。 例如:建立文件夹 parsing.asp,在 parsing.asp 文件夹内新建一个文本文档 test.txt,其内容为&…...
2023 年腾讯云轻量和CVM服务器租用价格表出炉(CPU/内存/带宽/系统盘)
腾讯云服务器的价格表是用户比较关心的问题,服务器的价格组成包括云服务器的机型价格、磁盘价格和宽带价格,主机教程网来详细说下腾讯云最新的云服务器价格表。我们以北京一区、Linux系统的云服务器为例,其他地域的价格会有所差异,…...

Java学习之路002——面向对象编程
【说明】部分内容来源于网络,如有冲突,请联系作者删除。 一、面向对象编程(OOP) 2.1 对象和类的关系 2.2 面向对象的特征 2.2.1 封装 2.2.2 继承 2.2.3 多态 3、抽象 使用abstract关键字修饰的类或者方法 定义抽象类(使用abstract) // 1、定义抽象方法…...

VR直播丨颠覆性技术革命,新型直播已经到来
细数当下最火热的营销手段,首先浮现脑海的无疑是“直播”。前有罗永浩、李佳琦,后有刘畊宏和东方甄选,直播如日中天,俨然成了大众足不出户就能休闲娱乐的重要途径。 而随着虚拟现实在“十四五规划”中被列入“建设数字中国”数字…...

【微信小程序】-- WXSS 模板样式- rpx import (十三)
💌 所属专栏:【微信小程序开发教程】 😀 作 者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! &…...

Biotin-PEG-SVA,生物素聚乙二醇琥珀酰亚胺戊酸酯,可用于检测或分子标记
Biotin-PEG-SVA 结构式:PEG分子量: 1000,2000,3400,5000,10000中文名称:生物素聚乙二醇琥珀酰亚胺戊酸酯,生物素-PEG-琥珀酰亚胺戊酸酯英文名称:Biotin-PEG-SVA …...

云原生是什么?核心概念和应用方法解析
什么是云原生? 云原生是一种基于容器、微服务和自动化运维的软件开发和部署方法。它可以使应用程序更加高效、可靠和可扩展,适用于各种不同的云平台。 如果要更直接通俗的来解释下上面的概念。云原生更准确来说就是一种文化,是一种潮流&…...

Editor工具开发实用篇:EditorGUI/EditorGUILayout的区别和EditorGUILayout的方法介绍
目录 一:EditorGUI和EditorGUILayout区别 二:EditorGUILayout 1.EditorGUILayout.BeginFadeGroup(float value); 2.EditorGUILayout.BeginHorizontal EditorGUILayout.BeginVertical 3.EditorGUILayout.BeginScrollView 4.EditorGUILayout.BeginT…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...

使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...

优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...

使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...

初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...