動態(tài)代理應用非常的廣泛,在各種開源的框架中都能看到他們的身影,比如spring中的aop使用動態(tài)代理增強,mybatis中使用動態(tài)代理生成mapper,動態(tài)代理主要有JDK和CGLIB兩種方式,今天來學習下這兩種方式的實現(xiàn),以及它們的優(yōu)缺點
動態(tài)代理:是使用反射和字節(jié)碼的技術,在運行期創(chuàng)建指定接口或類的子類,以及其實例對象的技術,通過這個技術可以無侵入的為代碼進行增強
基于 Spring Boot + MyBatis Plus + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權限、多租戶、數(shù)據(jù)權限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
一、JDK實現(xiàn)的動態(tài)代理
1、解析
jdk實現(xiàn)的動態(tài)代理由兩個重要的成員組成,分別是Proxy、InvocationHandler
Proxy: 是所有動態(tài)代理的父類,它提供了一個靜態(tài)方法來創(chuàng)建動態(tài)代理的class對象和實例
InvocationHandler: 每個動態(tài)代理實例都有一個關聯(lián)的InvocationHandler,在代理實例上調用方法是,方法調用將被轉發(fā)到InvocationHandler的invoke方法
2、簡單看下jdk的動態(tài)代理的原理圖

3、代碼實現(xiàn)
現(xiàn)在模擬一個用戶注冊的功能,動態(tài)代理對用戶的注冊功能進行增強,會判斷用戶名和密碼的長度,如果用戶名<=1和密碼<6則會拋出異常
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];
???//進行增強判斷
???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);
??//動態(tài)代理類
??UserService?userServiceProxy?=?(UserService)Proxy.newProxyInstance(delegate.getClass().getClassLoader(),
????delegate.getClass().getInterfaces(),?userServiceInterceptor);
??System.out.println("動態(tài)代理類:"+userServiceProxy.getClass());
??userServiceProxy.addUser(user);
?}
}
運行結果:當密碼的長度小于6時

這里就起到了動態(tài)增強的作用,mybatis的使用中我們知道不需要創(chuàng)建dao中的mapper接口的子類,也能調用到相應的方法,其實就是生成的實現(xiàn)了mapper接口的動態(tài)的代理類,我們可以去看看它的這個方法

接下來我們看下cglib的使用
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權限、多租戶、數(shù)據(jù)權限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://gitee.com/zhijiantianya/yudao-cloud
視頻教程:https://doc.iocoder.cn/video/
二、CGLIB動態(tài)代理
1、解析
CGLIB(Code Generation Library)是一個基于ASM的字節(jié)碼生成庫,它允許我們在運行時對字節(jié)碼進行修改和動態(tài)生成。CGLIB通過繼承的方式實現(xiàn)代理(最后這部分我們深思一下,它可能有哪些局限,final方法是不能夠被重寫,所以它不能增強被final修飾的方法,這個等下我們來驗證)
CGLIB的實現(xiàn)也有兩個重要的成員組成,Enhancer、MethodInterceptor,其實這兩個的使用和jdk實現(xiàn)的動態(tài)代理的Proxy、InvocationHandler非常相似
Enhancer: 來指定要代理的目標對象,實際處理代理邏輯的對象,最終通過調用create()方法得到代理對象、對這個對象所有的非final方法的調用都會轉發(fā)給MethodInterceptor
MethodInterceptor: 動態(tài)代理對象的方法調用都會轉發(fā)到intercept方法進行增強
2、圖解

3、代碼的實現(xiàn)
還是上面的場景,注冊用戶進行攔截增強,部分代碼如下
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];
???//進行增強判斷
???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);
??//動態(tài)代理類
??UserServiceImplCglib?cglibProxy?=?(UserServiceImplCglib)enhancer.create();
??System.out.println("動態(tài)代理類父類:"+cglibProxy.getClass().getSuperclass());
??cglibProxy.addUser(user);
?}
}
運行結果:

這里順便打印了動態(tài)代理類的父類,接下來我們將它的父類UserServiceImplCglib的addUser方法用final修飾,看看是否會被增強
UserServiceImplCglib.java
package?com.taolong.cglib;
?
import?com.taolong.User;
?
public?class?UserServiceImplCglib?{
?
?final?void?addUser(User??user)?{
??System.out.println("cglib...正在注冊用戶,用戶信息為:"+user);
?}
}
運行結果:

總結一下
1、JDK原聲動態(tài)代理時java原聲支持的、不需要任何外部依賴、但是它只能基于接口進行代理(因為它已經繼承了proxy了,java不支持多繼承)
2、CGLIB通過繼承的方式進行代理、無論目標對象沒有沒實現(xiàn)接口都可以代理,但是無法處理final的情況(final修飾的方法不能被覆寫)
編輯:黃飛
電子發(fā)燒友App








評論