動(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í)例對(duì)象的技術(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對(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è)用戶注冊(cè)的功能,動(dòng)態(tài)代理對(duì)用戶的注冊(cè)功能進(jìn)行增強(qiáng),會(huì)判斷用戶名和密碼的長(zhǎng)度,如果用戶名<=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è)用戶,用戶信息為:"+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("用戶名長(zhǎng)度必須大于1");
???}
???if?(user.getPassword().length()?<=?6)?{
????throw?new?RuntimeException("密碼長(zhǎng)度必須大于6");
???}
??}
??Object?result?=?method.invoke(realObj,?args);
??System.out.println("用戶注冊(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);
??//被代理類
??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)密碼的長(zhǎ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é)碼生成庫(kù),它允許我們?cè)谶\(yùn)行時(shí)對(duì)字節(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)對(duì)象,實(shí)際處理代理邏輯的對(duì)象,最終通過調(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è)用戶進(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("用戶名長(zhǎng)度必須大于1");
???}
???if?(user.getPassword().length()?<=?6)?{
????throw?new?RuntimeException("密碼長(zhǎng)度必須大于6");
???}
??}
??Object?result?=?method.invoke(realObject,?args);
??System.out.println("用戶注冊(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)代理類
??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...正在注冊(cè)用戶,用戶信息為:"+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)對(duì)象沒有沒實(shí)現(xiàn)接口都可以代理,但是無法處理final的情況(final修飾的方法不能被覆寫)
編輯:黃飛
電子發(fā)燒友App





















評(píng)論