专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

从源码角度讲述动态代理的实现

  一、引言
  在Spring中,最重要的应该当属IOC和AOP了,IOC的源码流程还比较简单,但AOP的流程就较为抽象了。
  其中,AOP中代理模式的重要性不言而喻,但对于没了解过代理模式的人来说,痛苦至极
  于是,我就去看了动态代理的实现,发现网上大多数文章讲的都是不清不楚,甚至讲了和没讲似的,让我极其难受
  本着咱们方向主打的就是源码,直接从从源码角度讲述一下代理模式
  兄弟们系好安全带,准备发车!
  注意:本文篇幅较长,请留出较长时间来阅读二、定义
  代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
  举个生活中常见的例子:客户想买房,房东有很多房,提供卖房服务,但房东不会带客户看房,于是客户通过中介买房。
  这时候对于房东来说,不直接和客户沟通,而是交于中介进行代理
  对于中介来说,她也会在原有的基础上收取一定的中介费三、静态代理
  我们创建Landlord接口如下:publicinterfaceLandlord{出租房子voidapartmentToRent();}复制代码
  创建其实现类HangZhouLandlord代表杭州房东出租房子publicclassHangZhouLandlordimplementsLandlord{OverridepublicvoidapartmentToRent(){System。out。println(杭州房东出租房子);}}复制代码
  创建代理类LandlordProxy,代表中介服务publicclassLandlordProxy{publicLandlordlandlord;publicLandlordProxy(Landlordlandlord){this。landlordlandlord;}publicvoidapartmentToRent(){apartmentToRentBefore();landlord。apartmentToRent();apartmentToRentAfter();}publicvoidapartmentToRentBefore(){System。out。println(出租房前,收取中介费);}publicvoidapartmentToRentAfter(){System。out。println(出租房后,签订合同);}}复制代码
  创建最终测试:publicclassJavaMain{publicstaticvoidmain(String〔〕args){LandlordlandlordnewHangZhouLandlord();LandlordProxyproxynewLandlordProxy(landlord);从中介进行租房proxy。apartmentToRent();}}复制代码
  得出最终结果:出租房前,收取中介费杭州房东出租房子出租房后,签订合同复制代码
  通过上述demo我们大概了解代理模式是怎么一回事优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展缺点:代理对象需要与目标对象实现一样的接口,所以会有很多代理类,一旦接口增加方法,目标对象与代理对象都要维护四、动态代理
  动态代理利用了JDKAPI,动态地在内存中构建代理对象,从而实现对目标对象的代理功能,动态代理又被称为JDK代理或接口代理。
  静态代理与动态代理的区别:静态代理在编译时就已经实现了,编译完成后代理类是一个实际的class文件动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中1、JDK代理
  代码如下:publicclassProxyFactory{目标方法publicObjecttarget;publicProxyFactory(Objecttarget){this。targettarget;}publicObjectgetProxyInstance(){returnProxy。newProxyInstance(目标对象的类加载器target。getClass()。getClassLoader(),目标对象的接口类型target。getClass()。getInterfaces(),事件处理器newInvocationHandler(){paramproxy代理对象parammethod代理对象调用的方法paramargs代理对象调用方法时实际的参数returnthrowsThrowableOverridepublicObjectinvoke(Objectproxy,Methodmethod,Object〔〕args)throwsThrowable{System。out。println(我是前置增强);method。invoke(target,args);System。out。println(我是后置增强);returnnull;}});}}复制代码
  我们测试一下:publicclassJavaMain{publicstaticvoidmain(String〔〕args){LandlordlandlordnewHangZhouLandlord();System。out。println(landlord。getClass());Landlordproxy(Landlord)newProxyFactory(landlord)。getProxyInstance();proxy。apartmentToRent();System。out。println(proxy。getClass());while(true){}}}复制代码
  得出结果:classcom。company。proxy。HangZhouLandlord我是前置增强杭州房东出租房子我是后置增强classcom。sun。proxy。Proxy0复制代码
  这里可能有小伙伴已经懵了,接着往后看1。1JDK类的动态生成
  Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:通过一个类的全限定名来获取定义此类的二进制字节流将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构在内存中生成一个代表这个类的java。lang。Class对象,作为方法区这个类的各种数据访问入口
  由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:
  从本地获取从网络中获取运行时计算生成,这种场景使用最多的是动态代理技术,在java。lang。reflect。Proxy类中,就是用了ProxyGenerator。generateProxyClass来为特定接口生成形式为Proxy的代理类的二进制字节流所以,动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用1。2JDK动态代理流程
  所以,我们可以得出一个结论:我们上面的Proxy0实际上是JVM在编译时期加载出来的类,由于这个类是编译时期加载的,所以我们没办法在IDEA里面看到。
  可能一般的文章,到这里基本就结束了,让大家知道Proxy0是由JVM编译时期加载出来的类
  但大家都知道,小黄的文章主打的就是一个硬核、源码级。所以,我们直接去看Proxy0的源代码
  首先,我们需要下载一个arthas的产品,网址:arthas。aliyun。comdoc,跟随流程解压
  Arthas是一款线上监控诊断产品,通过全局视角实时查看应用load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
  当我们一切准备完成后,启动我们上面动态代理的测试JavaMain类
  启动完成后,进入我们的arthas页面,执行命令:javajararthasboot。jar
  我们可以看到,我们的目标类com。company。proxy。JavaMain就出现了,随后我们按下4,进入到我们的监控页面。
  随后使用jadcom。sun。proxy。Proxy0之后,可以看到我们已经解析出来Proxy0的源码了
  我们将其复制到下面,并删减一些不必要的信息。publicfinalclassProxy0extendsProxyimplementsLandlord{privatestaticMethodm3;Proxy0类的构造方法参数为invocationHandlerpublicProxy0(InvocationHandlerinvocationHandler){super(invocationHandler);}static{m3Class。forName(com。company。proxy。Landlord)。getMethod(apartmentToRent,newClass〔0〕);}publicfinalvoidapartmentToRent(){this。h。invoke(this,m3,null);return;}}复制代码
  我们先看其有参构造方法,可以看到Proxy0的构造方法入参为InvocationHandler,有没有感觉似曾相识。
  如果你这里忘掉了,不妨去看一下动态代理的ProxyFactory的代码,可以发现,我们Proxy。newProxyInstance()的第三个自定义的参数,也正是我们的InvocationHandler。
  我们猜测一下,如果这里的传的InvocationHandler是我们之前自定义的InvocationHandler
  那么,如果我调用Proxy0。apartmentToRent()是不是就是执行下面的代码:publicfinalvoidapartmentToRent(){this。h。invoke(this,m3,null);return;}这里的h。invoke执行的是我们这里自定义的方法,然后进行的前后增强publicObjectgetProxyInstance(){returnProxy。newProxyInstance(target。getClass()。getClassLoader(),target。getClass()。getInterfaces(),newInvocationHandler(){OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object〔〕args)throwsThrowable{System。out。println(我是前置增强);method。invoke(target,args);System。out。println(我是后置增强);returnnull;}});复制代码
  如果说我们这个猜测是正确的话,那么会得出这样的几个结论:我们的代理类实际上是实现了Landlord的接口,然后重写了Landlord接口中的apartmentToRent方法当外界调用代理类的apartmentToRent()方法时,实际上是调用的我们自定义的newInvocationHandler()类里面的invoke方法
  还有我们的最后一步,也就是证明Proxy0的构造入参InvocationHandler就是我们自定义的InvocationHandler,废话不多说,直接来看代理的源码。returnProxy。newProxyInstance(ClassLoader,Interfaces,newInvocationHandler(){});publicstaticObjectnewProxyInstance(ClassLoaderloader,Classlt;?〔〕interfaces,InvocationHandlerh){clclasscom。sun。proxy。Proxy0Classlt;?clgetProxyClass0(loader,intfs);conspubliccom。sun。proxy。Proxy0(java。lang。reflect。InvocationHandler)finalConstructorlt;?conscl。getConstructor(constructorParams);根据构造参数实例化对象returncons。newInstance(newObject〔〕{h});}复制代码
  我们通过源码可以看到,一共分为三步(下面为反射的内容,如不熟悉可提前学习下反射):拿到Proxy0的Class根据Class拿到其构造方法根据构造方法传入参数进行实例化
  这就确定了我们上述的猜想是正确的。2、Cglib代理
  cglib(CodeGenerationLibrary)是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。cglib为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
  最底层是字节码ASM是操作字节码的工具cglib基于ASM字节码工具操作字节码(即动态生成代理,对方法进行增强)SpringAOP基于cglib进行封装,实现cglib方式的动态代理
  使用cglib需要引入cglib的jar包,如果你已经有springcore的jar包,则无需引入,因为spring中包含了cglib。cglib的Maven坐标cglibcglibartifactId3。2。5复制代码2。1cglib动态代理实现
  还是同样的配方,我们要创建一个需要代理的类(UserServiceImpl),但不需要实现任何的接口,因为我们的cglib是根据类来进行创建的。
  UserServiceImplpublicclassUserServiceImpl{查询功能ListStringfindUserList(){returnCollections。singletonList(小A);}}复制代码
  实现cglib的工厂类:UserLogProxypublicclassUserLogProxyimplementsMethodInterceptor{生成CGLIB动态代理类方法paramtargetreturnpublicObjectgetLogProxy(Objecttarget){增强器类,用来创建动态代理类EnhancerenhancernewEnhancer();设置代理类的父类字节码对象enhancer。setSuperclass(target。getClass());设置回调enhancer。setCallback(this);创建动态代理对象并返回returnenhancer。create();}paramo代理对象parammethod目标对象中的方法的Method实例paramobjects实际参数parammethodProxy代理类对象中的方法的Method实例returnthrowsThrowableOverridepublicObjectintercept(Objecto,Methodmethod,Object〔〕objects,MethodProxymethodProxy)throwsThrowable{System。out。println(前置输出);ObjectresultmethodProxy。invokeSuper(o,objects);returnresult;}}复制代码
  测试程序:JavaMainTestpublicclassJavaMainTest{publicstaticvoidmain(String〔〕args){目标对象UserServiceImpluserServicenewUserServiceImpl();System。out。println(userService。getClass());代理对象UserServiceImplproxy(UserServiceImpl)newUserLogProxy()。getLogProxy(userService);System。out。println(proxy。getClass());ListStringlistproxy。findUserList();System。out。println(用户信息:list);while(true){}}}复制代码
  结果:classcom。study。spring。proxy。UserServiceImplclasscom。study。spring。proxy。UserServiceImplEnhancerByCGLIBcd9788d前置输出用户信息:〔小A〕复制代码2。2cglib代理流程
  按照上述我们分析Proxy0的方法,将com。study。spring。proxy。UserServiceImplEnhancerByCGLIBcd9788d取出,得到如下:publicclassUserServiceImplEnhancerByCGLIBcd9788dextendsUserServiceImplimplementsFactory{finalListfindUserList(){是否设置了回调MethodInterceptormethodInterceptorthis。CGLIBCALLBACK0;if(methodInterceptornull){UserServiceImplEnhancerByCGLIBcd9788d。CGLIBBINDCALLBACKS(this);methodInterceptorthis。CGLIBCALLBACK0;}设置回调,需要调用intercept方法if(methodInterceptor!null){return(List)methodInterceptor。intercept(this,CGLIBfindUserList0Method,CGLIBemptyArgs,CGLIBfindUserList0Proxy);}无回调,调用父类的findUserList即可returnsuper。findUserList();}finalListCGLIBfindUserList0(){returnsuper。findUserList();}}复制代码
  博主先把整个流程图放到下面,然后结合流程图来进行讲解:
  在JVM编译期间,我们的Enhancer会根据目标类的信息去动态的生成动态代理类并设置回调当用户在通过上述的动态代理类执行findUserList()方法时,有两个执行选项若设置了回调接口,则直接调用UserLogProxy中的intercept,然后通过FastClass类调用动态代理类,执行CGLIBfindUserList0方法,调用父类的findUserList()方法若没有设置回调接口,则直接调用父类的findUserList()方法五、代理模式总结1、三种代理模式实现方式的对比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)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题2、代理模式优缺点
  优点:代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;代理对象可以扩展目标对象的功能;代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
  缺点:增加了系统的复杂度;3、代理模式使用场景功能增强当需要对一个对象的访问提供一些额外操作时,可以使用代理模式远程(Remote)代理实际上,RPC框架也可以看作一种代理模式,GoF的《设计模式》一书中把它称作远程代理。通过远程代理,将网络通信、数据编解码等细节隐藏起来。客户端在使用RPC服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。除此之外,RPC服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注跟客户端的交互细节。防火墙(Firewall)代理当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。保护(ProtectorAccess)代理控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。六、结尾
  终于写完了这篇文章,动态代理在我看AOP源码时,就感觉挺抽象的
  我感觉最大的原因应该在于:代理类动态生成,无法查看,导致对其模糊,从而陷入不理解
  但通过这篇文章,我相信,99的人应该都可以理解了动态代理模式的来龙去脉
  当然,好刀要用在刀刃上,在面试中,若面试官提及设计模式、动态代理、Spring、Dubbo都可以引出动态代理,基本这篇文章无差别秒杀
  如果你能看到这,那博主必须要给你一个大大的鼓励,谢谢你的支持!
  喜欢的可以点个关注,后续会更新Spring源码系列文章
  我们下期再见。

法国赛国羽收获双冠,何冰娇逆转马林,雅思挽救4赛点登顶北京时间10月31日,2022年世界羽联世界巡回赛法国公开赛落下帷幕,中国队获得两项冠军。何冰娇在先丢一局后连扳两局,她逆转战胜西班牙的马林夺得女单冠军。郑思维黄雅琼在决胜局连救4国货美妆再现面膜爆品!自然堂携手王一博引爆话题讨论热度自然堂安瓶系列堪称是面膜品类的爆款制造机,王一博同款烟酰胺安瓶面膜更是爆中之爆。2022年10月,自然堂再次携手面膜全球代言人王一博带来新品自然堂酵母安瓶面膜,一经发布就成为微博热这是什么热度?华为Mate40Pro官翻版上线1分钟售罄手机中国新闻今日早些时候,手机中国了解到华为官方翻新机Mate40Pro5G已上架华为商城,并在今晚8点正式开售,其中8GB128GB售价6199元,8GB256GB售价6699元关于新乡总决赛现场观众被吐槽素质差这件事一些自己的看法新乡WTT世界杯总决赛已经圆满结束了,但关于它的讨论并没有停止,除了王楚钦心态张本智和吵等关于运动员的话题被人们热议外,本次比赛的观众也成功占据了球迷的视角。许多人认为现场观众素质不敢老去的一代人我在头条搞创作第二期每个人都会进入老龄化。自然规律嘛!到了进入老年的阶段就勇于面对,勇于承认,用轻松惬意的心情泰然处之。身为六零后的我工作已面临退休,子女长大成人,应该是安享晚年的语言的魔力你可不要小看信念的力量文叶音你听过这样的故事吗?收养了婴儿的妈妈,由于她相信妈妈应当用母乳喂养婴儿,于是她开始分泌乳汁,并有了足够的奶水来自己喂养收养的孩子。这个故事证实了信念所产生的期望会影响我们深层米切尔一举成为骑士领军人物,新东家竟然奖励他一条狗链?差不多一周前,在骑士加时赛击败奇才那一战之后,骑士主教练比克斯塔夫走进球员更衣室,从自己的柜子里拿出了球队那条著名的垃圾场狗链,然后对所有的球员们说我们要把这个荣誉,送给这位打了4觉者张锐的心安中国人对现实生活对自己人生目标的追求各不相同,但有一个共同的愿念,那就是都希望能够心安理得的过好这一生,但何其难哉,达成这个境界者万不及一。原因就在于自己心不定,身无根,无长技,大夜读一个人的顶级自律,是坚持这4件事夜读一个人的顶级自律,是坚持这4件事专注当下与其沉浸在无法改变的过去,或是难以预知的未来,不如专注于每一个当下。任何事情都需要一个积累的过程,既不能急于求成,也不可放任自流。用好眼记住这五点,学会真正地爱自己王尔德说爱自己是终身浪漫的开始。爱自己,就是接纳自己的不完美,每个人都有自己的优缺点,只有你先爱自己,别人才有可能爱你。一个人只有做到接受自己,才有可能做到真正的改变。该努力使拼尽雷军的小目标做世界TOP5,每年至少1000万辆2016年,万达集团董事长王健林在接受一个视频采访栏目中表示先定一个能达到的小目标,比方说我先赚它一个亿。这句话火遍全网。近日,小米科技创始人董事长兼CEO雷军日前在美国社交网络T
宝宝乳糖不耐,应该怎么办?宝妈请留心!撰文森今天要和大家说聊聊的是关于新生儿乳糖不耐的问题,中招的身体会传递出什么样的信号呢?因为新生期的宝宝还没开口说话,对于宝宝的身体情况只能通过家长们对宝宝身体的新表现来分析。如今给宝宝冲奶粉用什么水好开始给宝宝调奶粉的时候,我是想着自来水不干净,没有矿泉水好。然后直到我看了一些育儿知识,才明白用矿泉水给宝宝冲奶粉并不好。医生也说过矿泉水本身有丰富的矿物质成分,宝宝肠胃消化功能不阔别舞台十年,潮剧夫妻档直播间再就业,靠打赏养活全家十年前离开潮剧舞台的时候,陈继珊和丈夫从没想过有一天还会回来。更没有想到过,他们会把寄托着思乡的潮剧,唱给远在新加坡柬埔寨等地的海外潮汕人听。十年里,陈继珊和丈夫经历了辞职创业创业网传白夜追凶2改名,原班人马出演即将开机白夜追凶是由王伟导演执导,潘粤明王泷正梁缘等领衔主演的悬疑推理剧,2017年一经播出就收获了大量好评口碑居高不下,豆瓣评分破9,热度一路飙升,还让早已淡出大众视野的潘粤明事业重回巅女主梅婷光着身子问黄渤你还爱我吗?你能接受这种表达吗?打开生活的正确方式征文近日看了一部连续剧打开生活的方式,没想到却被其中一段戏给惊艳到。梅婷饰演的女主质问男主黄渤你还爱我吗?一边脱衣服一边哭,非常的委屈。可是男主黄渤却看都不看她一女星用1亿日元给大长腿投保,修长大腿太吸睛,梨形身材要这样瘦在演艺圈中,颜值和身材会为明星们带来更多流量和粉丝,特别是对于女明星来说,拥有好身材和高颜值,会让她们的演艺道路走得更容易。所以,女明星为自己的身材和颜值进行投保并不罕见。因为她们事实证明,失去金星,是整个娱乐圈的悲哀文9号探秘人编辑9号探秘人你此生要记住三个人生你的妈,养你的老婆,教你的金姐。这是国内著名舞蹈家艺术家金星说的一句话。作为娱乐圈里为数不多敢说真话的人,金星颇受观众喜爱。她说话做事今晚,听见我们的青春!今天是3月1日2023的进度条已过完近六分之一今年你有哪些目标要实现?青春时光里,哪个瞬间曾闪闪发光?今晚8点由央视新闻央视网联合出品的中央广播电视总台青春分享节目青春2023温暖岂止是相形见绌我们的日子与人世间几乎是一集不落地追完了近期央视热播剧我们的日子,甚失所望。与另一部之前热播的人世间相比,不只是相形见绌。两部剧酷似孪生姐妹,我们的日子可以说是瘦身版的人世间。两部剧的背景地域都是东央视热播剧敷衍了事,在我们的日子身上体现的淋漓尽致央视一台为什么要播烂剧我们的日子?说话间电视剧我们的日子就迎来了大结局,相对观众对其他热播剧结局的依依不舍,我们的日子结局之后,大家反而都松了一口气,如释重负终于大结局了!不少观众西游记原班人马出演,上映3天票房5000元,事实证明卖情怀难西游记的热度,每年都是影视化的热门。大圣归来大闹天宫三打白骨精女儿国。。甚至,大闹天竺还不忘蹭一下西游记的热度。没想到,最近又有一部电影打着西游记旗号的电影悄然上映。请来了孙悟空的
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网