動(dòng)態(tài)代理應(yīng)用非常的廣泛,在各種開(kāi)源的框架中都能看到他們的身影,比如spring中的aop使用動(dòng)態(tài)代理增強(qiáng),mybatis中使用動(dòng)態(tài)代理生成mapper,動(dòng)態(tài)代理主要有JDK和CGLIB兩種方式,今天來(lái)學(xué)習(xí)下這兩種方式的實(shí)現(xiàn),以及它們的優(yōu)缺點(diǎn)
動(dòng)態(tài)代理:是使用反射和字節(jié)碼的技術(shù),在運(yùn)行期創(chuàng)建指定接口或類(lèi)的子類(lèi),以及其實(shí)例對(duì)象的技術(shù),通過(guò)這個(gè)技術(shù)可以無(wú)侵入的為代碼進(jìn)行增強(qiáng)
基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶(hù)小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶(hù)、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項(xiàng)目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
一、JDK實(shí)現(xiàn)的動(dòng)態(tài)代理
1、解析
jdk實(shí)現(xiàn)的動(dòng)態(tài)代理由兩個(gè)重要的成員組成,分別是Proxy、InvocationHandler
Proxy: 是所有動(dòng)態(tài)代理的父類(lèi),它提供了一個(gè)靜態(tài)方法來(lái)創(chuàng)建動(dòng)態(tài)代理的class對(duì)象和實(shí)例
InvocationHandler: 每個(gè)動(dòng)態(tài)代理實(shí)例都有一個(gè)關(guān)聯(lián)的InvocationHandler,在代理實(shí)例上調(diào)用方法是,方法調(diào)用將被轉(zhuǎn)發(fā)到InvocationHandler的invoke方法
2、簡(jiǎn)單看下jdk的動(dòng)態(tài)代理的原理圖
3、代碼實(shí)現(xiàn)
現(xiàn)在模擬一個(gè)用戶(hù)注冊(cè)的功能,動(dòng)態(tài)代理對(duì)用戶(hù)的注冊(cè)功能進(jìn)行增強(qiáng),會(huì)判斷用戶(hù)名和密碼的長(zhǎng)度,如果用戶(hù)名<=1和密碼<6則會(huì)拋出異常
User.java
package?com.taolong; ? public?class?User?{ ? ?private?String?name; ? ?private?Integer?age; ? ?private?String?password; ? ?public?String?getName()?{ ??return?name; ?} ? ?public?void?setName(String?name)?{ ??this.name?=?name; ?} ? ? ?public?Integer?getAge()?{ ??return?age; ?} ? ?public?void?setAge(Integer?age)?{ ??this.age?=?age; ?} ? ?public?String?getPassword()?{ ??return?password; ?} ? ?public?void?setPassword(String?password)?{ ??this.password?=?password; ?} ? ?@Override ?public?String?toString()?{ ??return?"User?[name="?+?name?+?",?age="?+?age?+?",?password="?+?password?+?"]"; ?} ? }
UserService.java
package?com.taolong.jdk; ? import?com.taolong.User; ? public?interface?UserService?{ ? ?void?addUser(User?user); }
UserServiceImpl.java
package?com.taolong.jdk; import?com.taolong.User; public?class?UserServiceImpl?implements?UserService?{ ???@Override ???public?void?addUser(User?user)?{ ????System.out.println("jdk...正在注冊(cè)用戶(hù),用戶(hù)信息為:"+user); ???} }
UserServiceInterceptor.java
package?com.taolong.jdk; ? import?java.lang.reflect.InvocationHandler; import?java.lang.reflect.Method; ? import?com.taolong.User; ? public?class?UserServiceInterceptor?implements?InvocationHandler?{ ? ?private?Object?realObj; ? ?public?UserServiceInterceptor(Object?realObject)?{ ??super(); ??this.realObj?=?realObject; ?} ? ?@Override ?public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{ ??if?(args!=null?&&?args.length?>?0?&&?args[0]?instanceof?User)?{ ???User?user?=?(User)args[0]; ???//進(jìn)行增強(qiáng)判斷 ???if?(user.getName().length()?<=?1)?{ ????throw?new?RuntimeException("用戶(hù)名長(zhǎng)度必須大于1"); ???} ???if?(user.getPassword().length()?<=?6)?{ ????throw?new?RuntimeException("密碼長(zhǎng)度必須大于6"); ???} ??} ??Object?result?=?method.invoke(realObj,?args); ??System.out.println("用戶(hù)注冊(cè)成功..."); ??return?result; ?} ? ?public?Object?getRealObj()?{ ??return?realObj; ?} ? ?public?void?setRealObj(Object?realObj)?{ ??this.realObj?=?realObj; ?} ? }
ClientTest.java
package?com.taolong.jdk; ? import?java.lang.reflect.InvocationHandler; import?java.lang.reflect.Proxy; ? import?com.taolong.User; ? public?class?ClientTest?{ ? ?public?static?void?main(String[]?args)?{ ??User?user?=?new?User(); ??user.setName("hongtaolong"); ??user.setPassword("hong"); ??user.setAge(23); ??//被代理類(lèi) ??UserService?delegate?=?new?UserServiceImpl(); ??InvocationHandler?userServiceInterceptor?=?new?UserServiceInterceptor(delegate); ??//動(dòng)態(tài)代理類(lèi) ??UserService?userServiceProxy?=?(UserService)Proxy.newProxyInstance(delegate.getClass().getClassLoader(), ????delegate.getClass().getInterfaces(),?userServiceInterceptor); ??System.out.println("動(dòng)態(tài)代理類(lèi):"+userServiceProxy.getClass()); ??userServiceProxy.addUser(user); ?} }
運(yùn)行結(jié)果:當(dāng)密碼的長(zhǎng)度小于6時(shí)
這里就起到了動(dòng)態(tài)增強(qiáng)的作用,mybatis的使用中我們知道不需要?jiǎng)?chuàng)建dao中的mapper接口的子類(lèi),也能調(diào)用到相應(yīng)的方法,其實(shí)就是生成的實(shí)現(xiàn)了mapper接口的動(dòng)態(tài)的代理類(lèi),我們可以去看看它的這個(gè)方法
接下來(lái)我們看下cglib的使用
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶(hù)小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶(hù)、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項(xiàng)目地址:https://gitee.com/zhijiantianya/yudao-cloud
視頻教程:https://doc.iocoder.cn/video/
二、CGLIB動(dòng)態(tài)代理
1、解析
CGLIB(Code Generation Library)是一個(gè)基于ASM的字節(jié)碼生成庫(kù),它允許我們?cè)谶\(yùn)行時(shí)對(duì)字節(jié)碼進(jìn)行修改和動(dòng)態(tài)生成。CGLIB通過(guò)繼承的方式實(shí)現(xiàn)代理(最后這部分我們深思一下,它可能有哪些局限,final方法是不能夠被重寫(xiě),所以它不能增強(qiáng)被final修飾的方法,這個(gè)等下我們來(lái)驗(yàn)證)
CGLIB的實(shí)現(xiàn)也有兩個(gè)重要的成員組成,Enhancer、MethodInterceptor,其實(shí)這兩個(gè)的使用和jdk實(shí)現(xiàn)的動(dòng)態(tài)代理的Proxy、InvocationHandler非常相似
Enhancer: 來(lái)指定要代理的目標(biāo)對(duì)象,實(shí)際處理代理邏輯的對(duì)象,最終通過(guò)調(diào)用create()方法得到代理對(duì)象、對(duì)這個(gè)對(duì)象所有的非final方法的調(diào)用都會(huì)轉(zhuǎn)發(fā)給MethodInterceptor
MethodInterceptor: 動(dòng)態(tài)代理對(duì)象的方法調(diào)用都會(huì)轉(zhuǎn)發(fā)到intercept方法進(jìn)行增強(qiáng)
2、圖解
3、代碼的實(shí)現(xiàn)
還是上面的場(chǎng)景,注冊(cè)用戶(hù)進(jìn)行攔截增強(qiáng),部分代碼如下
UserServiceCglibInterceptor.java
package?com.taolong.cglib; ? import?java.lang.reflect.Method; ? import?com.taolong.User; ? import?net.sf.cglib.proxy.MethodInterceptor; import?net.sf.cglib.proxy.MethodProxy; ? public?class?UserServiceCglibInterceptor?implements?MethodInterceptor?{ ? ?private?Object?realObject; ? ?public?UserServiceCglibInterceptor(Object?realObject)?{ ??super(); ??this.realObject?=?realObject; ?} ? ?@Override ?public?Object?intercept(Object?object,?Method?method,?Object[]?args,?MethodProxy?proxy)?throws?Throwable?{ ??if?(args!=null?&&?args.length?>?0?&&?args[0]?instanceof?User)?{ ???User?user?=?(User)args[0]; ???//進(jìn)行增強(qiáng)判斷 ???if?(user.getName().length()?<=?1)?{ ????throw?new?RuntimeException("用戶(hù)名長(zhǎng)度必須大于1"); ???} ???if?(user.getPassword().length()?<=?6)?{ ????throw?new?RuntimeException("密碼長(zhǎng)度必須大于6"); ???} ??} ??Object?result?=?method.invoke(realObject,?args); ??System.out.println("用戶(hù)注冊(cè)成功..."); ??return?result; ?} ? } ClientTest.java
package?com.taolong.cglib; ? import?com.taolong.User; ? import?net.sf.cglib.proxy.Enhancer; ? public?class?ClientTest?{ ? ?public?static?void?main(String[]?args)?{ ??User?user?=?new?User(); ??user.setName("hongtaolong"); ??user.setPassword("hong"); ??user.setAge(23); ??//被代理的對(duì)象 ??UserServiceImplCglib?delegate?=?new?UserServiceImplCglib(); ??UserServiceCglibInterceptor?serviceInterceptor?=?new?UserServiceCglibInterceptor(delegate); ??Enhancer?enhancer?=?new?Enhancer(); ??enhancer.setSuperclass(delegate.getClass()); ??enhancer.setCallback(serviceInterceptor); ??//動(dòng)態(tài)代理類(lèi) ??UserServiceImplCglib?cglibProxy?=?(UserServiceImplCglib)enhancer.create(); ??System.out.println("動(dòng)態(tài)代理類(lèi)父類(lèi):"+cglibProxy.getClass().getSuperclass()); ??cglibProxy.addUser(user); ?} }
運(yùn)行結(jié)果:
這里順便打印了動(dòng)態(tài)代理類(lèi)的父類(lèi),接下來(lái)我們將它的父類(lèi)UserServiceImplCglib的addUser方法用final修飾,看看是否會(huì)被增強(qiáng)
UserServiceImplCglib.java
package?com.taolong.cglib; ? import?com.taolong.User; ? public?class?UserServiceImplCglib?{ ? ?final?void?addUser(User??user)?{ ??System.out.println("cglib...正在注冊(cè)用戶(hù),用戶(hù)信息為:"+user); ?} }
運(yùn)行結(jié)果:
總結(jié)一下
1、JDK原聲動(dòng)態(tài)代理時(shí)java原聲支持的、不需要任何外部依賴(lài)、但是它只能基于接口進(jìn)行代理(因?yàn)樗呀?jīng)繼承了proxy了,java不支持多繼承)
2、CGLIB通過(guò)繼承的方式進(jìn)行代理、無(wú)論目標(biāo)對(duì)象沒(méi)有沒(méi)實(shí)現(xiàn)接口都可以代理,但是無(wú)法處理final的情況(final修飾的方法不能被覆寫(xiě))
編輯:黃飛
評(píng)論