動態代理應用非常的廣泛,在各種開源的框架中都能看到他們的身影,比如spring中的aop使用動態代理增強,mybatis中使用動態代理生成mapper,動態代理主要有JDK和CGLIB兩種方式,今天來學習下這兩種方式的實現,以及它們的優缺點
動態代理:是使用反射和字節碼的技術,在運行期創建指定接口或類的子類,以及其實例對象的技術,通過這個技術可以無侵入的為代碼進行增強
基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
一、JDK實現的動態代理
1、解析
jdk實現的動態代理由兩個重要的成員組成,分別是Proxy、InvocationHandler
Proxy: 是所有動態代理的父類,它提供了一個靜態方法來創建動態代理的class對象和實例
InvocationHandler: 每個動態代理實例都有一個關聯的InvocationHandler,在代理實例上調用方法是,方法調用將被轉發到InvocationHandler的invoke方法
2、簡單看下jdk的動態代理的原理圖
3、代碼實現
現在模擬一個用戶注冊的功能,動態代理對用戶的注冊功能進行增強,會判斷用戶名和密碼的長度,如果用戶名<=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); ??//動態代理類 ??UserService?userServiceProxy?=?(UserService)Proxy.newProxyInstance(delegate.getClass().getClassLoader(), ????delegate.getClass().getInterfaces(),?userServiceInterceptor); ??System.out.println("動態代理類:"+userServiceProxy.getClass()); ??userServiceProxy.addUser(user); ?} }
運行結果:當密碼的長度小于6時
這里就起到了動態增強的作用,mybatis的使用中我們知道不需要創建dao中的mapper接口的子類,也能調用到相應的方法,其實就是生成的實現了mapper接口的動態的代理類,我們可以去看看它的這個方法
接下來我們看下cglib的使用
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://gitee.com/zhijiantianya/yudao-cloud
視頻教程:https://doc.iocoder.cn/video/
二、CGLIB動態代理
1、解析
CGLIB(Code Generation Library)是一個基于ASM的字節碼生成庫,它允許我們在運行時對字節碼進行修改和動態生成。CGLIB通過繼承的方式實現代理(最后這部分我們深思一下,它可能有哪些局限,final方法是不能夠被重寫,所以它不能增強被final修飾的方法,這個等下我們來驗證)
CGLIB的實現也有兩個重要的成員組成,Enhancer、MethodInterceptor,其實這兩個的使用和jdk實現的動態代理的Proxy、InvocationHandler非常相似
Enhancer: 來指定要代理的目標對象,實際處理代理邏輯的對象,最終通過調用create()方法得到代理對象、對這個對象所有的非final方法的調用都會轉發給MethodInterceptor
MethodInterceptor: 動態代理對象的方法調用都會轉發到intercept方法進行增強
2、圖解
3、代碼的實現
還是上面的場景,注冊用戶進行攔截增強,部分代碼如下
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); ??//動態代理類 ??UserServiceImplCglib?cglibProxy?=?(UserServiceImplCglib)enhancer.create(); ??System.out.println("動態代理類父類:"+cglibProxy.getClass().getSuperclass()); ??cglibProxy.addUser(user); ?} }
運行結果:
這里順便打印了動態代理類的父類,接下來我們將它的父類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原聲動態代理時java原聲支持的、不需要任何外部依賴、但是它只能基于接口進行代理(因為它已經繼承了proxy了,java不支持多繼承)
2、CGLIB通過繼承的方式進行代理、無論目標對象沒有沒實現接口都可以代理,但是無法處理final的情況(final修飾的方法不能被覆寫)
編輯:黃飛
評論
查看更多