動(dòng)態(tài)代理應(yīng)用非常的廣泛,在各種開源的框架中都能看到他們的身影,比如spring中的aop使用動(dòng)態(tài)代理增強(qiáng),mybatis中使用動(dòng)態(tài)代理生成mapper,動(dòng)態(tài)代理主要有JDK和CGLIB兩種方式,今天來學(xué)習(xí)下這兩種方式的實(shí)現(xiàn),以及它們的優(yōu)缺點(diǎn)
動(dòng)態(tài)代理:是使用反射和字節(jié)碼的技術(shù),在運(yùn)行期創(chuàng)建指定接口或類的子類,以及其實(shí)例對象的技術(shù),通過這個(gè)技術(shù)可以無侵入的為代碼進(jìn)行增強(qiáng)
基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(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)代理的父類,它提供了一個(gè)靜態(tài)方法來創(chuàng)建動(dòng)態(tài)代理的class對象和實(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、簡單看下jdk的動(dòng)態(tài)代理的原理圖
3、代碼實(shí)現(xiàn)
現(xiàn)在模擬一個(gè)用戶注冊的功能,動(dòng)態(tài)代理對用戶的注冊功能進(jìn)行增強(qiáng),會(huì)判斷用戶名和密碼的長度,如果用戶名<=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...正在注冊用戶,用戶信息為:"+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("用戶名長度必須大于1"); ???} ???if?(user.getPassword().length()?<=?6)?{ ????throw?new?RuntimeException("密碼長度必須大于6"); ???} ??} ??Object?result?=?method.invoke(realObj,?args); ??System.out.println("用戶注冊成功..."); ??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); ??//被代理類 ??UserService?delegate?=?new?UserServiceImpl(); ??InvocationHandler?userServiceInterceptor?=?new?UserServiceInterceptor(delegate); ??//動(dòng)態(tài)代理類 ??UserService?userServiceProxy?=?(UserService)Proxy.newProxyInstance(delegate.getClass().getClassLoader(), ????delegate.getClass().getInterfaces(),?userServiceInterceptor); ??System.out.println("動(dòng)態(tài)代理類:"+userServiceProxy.getClass()); ??userServiceProxy.addUser(user); ?} }
運(yùn)行結(jié)果:當(dāng)密碼的長度小于6時(shí)
這里就起到了動(dòng)態(tài)增強(qiáng)的作用,mybatis的使用中我們知道不需要?jiǎng)?chuàng)建dao中的mapper接口的子類,也能調(diào)用到相應(yīng)的方法,其實(shí)就是生成的實(shí)現(xiàn)了mapper接口的動(dòng)態(tài)的代理類,我們可以去看看它的這個(gè)方法
接下來我們看下cglib的使用
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(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é)碼生成庫,它允許我們在運(yùn)行時(shí)對字節(jié)碼進(jìn)行修改和動(dòng)態(tài)生成。CGLIB通過繼承的方式實(shí)現(xiàn)代理(最后這部分我們深思一下,它可能有哪些局限,final方法是不能夠被重寫,所以它不能增強(qiáng)被final修飾的方法,這個(gè)等下我們來驗(yàn)證)
CGLIB的實(shí)現(xiàn)也有兩個(gè)重要的成員組成,Enhancer、MethodInterceptor,其實(shí)這兩個(gè)的使用和jdk實(shí)現(xiàn)的動(dòng)態(tài)代理的Proxy、InvocationHandler非常相似
Enhancer: 來指定要代理的目標(biāo)對象,實(shí)際處理代理邏輯的對象,最終通過調(diào)用create()方法得到代理對象、對這個(gè)對象所有的非final方法的調(diào)用都會(huì)轉(zhuǎn)發(fā)給MethodInterceptor
MethodInterceptor: 動(dòng)態(tài)代理對象的方法調(diào)用都會(huì)轉(zhuǎn)發(fā)到intercept方法進(jìn)行增強(qiáng)
2、圖解
3、代碼的實(shí)現(xiàn)
還是上面的場景,注冊用戶進(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("用戶名長度必須大于1"); ???} ???if?(user.getPassword().length()?<=?6)?{ ????throw?new?RuntimeException("密碼長度必須大于6"); ???} ??} ??Object?result?=?method.invoke(realObject,?args); ??System.out.println("用戶注冊成功..."); ??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); ??//被代理的對象 ??UserServiceImplCglib?delegate?=?new?UserServiceImplCglib(); ??UserServiceCglibInterceptor?serviceInterceptor?=?new?UserServiceCglibInterceptor(delegate); ??Enhancer?enhancer?=?new?Enhancer(); ??enhancer.setSuperclass(delegate.getClass()); ??enhancer.setCallback(serviceInterceptor); ??//動(dòng)態(tài)代理類 ??UserServiceImplCglib?cglibProxy?=?(UserServiceImplCglib)enhancer.create(); ??System.out.println("動(dòng)態(tài)代理類父類:"+cglibProxy.getClass().getSuperclass()); ??cglibProxy.addUser(user); ?} }
運(yùn)行結(jié)果:
這里順便打印了動(dòng)態(tà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...正在注冊用戶,用戶信息為:"+user); ?} }
運(yùn)行結(jié)果:
總結(jié)一下
1、JDK原聲動(dòng)態(tài)代理時(shí)java原聲支持的、不需要任何外部依賴、但是它只能基于接口進(jìn)行代理(因?yàn)樗呀?jīng)繼承了proxy了,java不支持多繼承)
2、CGLIB通過繼承的方式進(jìn)行代理、無論目標(biāo)對象沒有沒實(shí)現(xiàn)接口都可以代理,但是無法處理final的情況(final修飾的方法不能被覆寫)
編輯:黃飛
評(píng)論
查看更多