<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          動態(tài)代理竟然如此簡單!

          共 14915字,需瀏覽 30分鐘

           ·

          2021-01-25 02:44



          這是Java建設者的第132篇原創(chuàng)文章
          https://github.com/crisxuan/bestJavaer
          歡迎讀者們 star 我的 github


          這篇文章我們來聊一下 Java 中的動態(tài)代理。

          動態(tài)代理在 Java 中有著廣泛的應用,比如?AOP 的實現(xiàn)原理、RPC遠程調用、Java 注解對象獲取、日志框架、全局性異常處理、事務處理等

          在了解動態(tài)代理前,我們需要先了解一下什么是代理模式。

          代理模式

          代理模式(Proxy Pattern)是 23 種設計模式的一種,屬于結構型模式。他指的是一個對象本身不做實際的操作,而是通過其他對象來得到自己想要的結果。這樣做的好處是可以在目標對象實現(xiàn)的基礎上,增強額外的功能操作,即擴展目標對象的功能

          這里能體現(xiàn)出一個非常重要的編程思想:不要隨意去改源碼,如果需要修改,可以通過代理的方式來擴展該方法。

          如上圖所示,用戶不能直接使用目標對象,而是構造出一個代理對象,由代理對象作為中轉,代理對象負責調用目標對象真正的行為,從而把結果返回給用戶。

          也就是說,代理的關鍵點就是代理對象和目標對象的關系

          代理其實就和經(jīng)紀人一樣,比如你是一個明星,有很多粉絲。你的流量很多,經(jīng)常會有很多金主來找你洽談合作等,你自己肯定忙不過來,因為你要處理的不只是談合作這件事情,你還要懂才藝、拍戲、維護和粉絲的關系、營銷等。為此,你找了一個經(jīng)紀人,你讓他負責和金主談合作這件事,經(jīng)紀人做事很認真負責,圓滿的完成了任務,于是,金主找你談合作就變成了金主和你的經(jīng)紀人談合作,你就有更多的時間來忙其他事情了。如下圖所示

          這是一種靜態(tài)代理,因為這個代理(經(jīng)紀人)是你自己親自挑選的。

          但是后來隨著你的業(yè)務逐漸拓展,你無法選擇每個經(jīng)紀人,所以你索性交給了代理公司來幫你做。如果你想在 B 站火一把,那就直接讓代理公司幫你找到負責營銷方面的代理人,如果你想維護和粉絲的關系,那你直接讓代理公司給你找一些托兒就可以了,那么此時的關系圖會變?yōu)槿缦?/p>

          此時你幾乎所有的工作都是由代理公司來進行打理,而他們派出誰來幫你做這些事情你就不得而知了,這得根據(jù)實際情況來定,因為代理公司也不只是負責你一個明星,而且每個人所擅長的領域也不同,所以你只有等到有實際需求后,才會給你指定對應的代理人,這種情況就叫做動態(tài)代理

          靜態(tài)代理

          從編譯期是否能確定最終的執(zhí)行方法可以把代理模式分為靜態(tài)代理和動態(tài)代理,我們先演示一下靜態(tài)代理,這里有一個需求,領導想在系統(tǒng)中添加一個用戶,但是他不自己添加,他讓下面的程序員來添加,我們看一下這個過程。

          首先構建一個用戶接口,定義一個保存用戶的模版方法。

          public?interface?UserDao?{

          ????void?saveUser();
          }

          構建一個用戶實現(xiàn)類,這個用戶實現(xiàn)類是真正進行用戶操作的方法

          public?class?UserDaoImpl?implements?UserDao{

          ????@Override
          ????public?void?saveUser()?{
          ????????System.out.println("?----?保存用戶?----?");
          ????}
          }

          構建一個用戶代理類,用戶代理類也有一個保存用戶的方法,不過這個方法屬于代理方法,它不會執(zhí)行真正的保存用戶,而是內部持有一個真正的用戶對象,進行用戶保存。

          public?class?UserProxy?{

          ????private?UserDao?userDao;
          ????public?UserProxy(UserDao?userDao){
          ????????this.userDao?=?userDao;
          ????}

          ????public?void?saveUser()?{
          ????????System.out.println("?----?代理開始?----?");
          ????????userDao.saveUser();
          ????????System.out.println("?----?代理結束?----");
          ????}
          }

          下面是測試方法。

          public?class?UserTest?{

          ????public?static?void?main(String[]?args)?{

          ????????UserDao?userDao?=?new?UserDaoImpl();
          ????????UserProxy?userProxy?=?new?UserProxy(userDao);
          ????????userProxy.saveUser();

          ????}
          }

          新創(chuàng)建一個用戶實現(xiàn)類 (UserDaoImpl),它不執(zhí)行用戶操作。然后再創(chuàng)建一個用戶代理(UserProxy),執(zhí)行用戶代理的用戶保存(saveUser),其內部會調用用戶實現(xiàn)類的保存用戶(saveUser)方法,因為我們 JVM 可以在編譯期確定最終的執(zhí)行方法,所以上面的這種代理模式又叫做靜態(tài)代理

          代理模式具有無侵入性的優(yōu)點,以后我們增加什么新功能的話,我們可以直接增加一個代理類,讓代理類來調用用戶操作,這樣我們就實現(xiàn)了不通過改源碼的方式增加了新的功能。然后生活很美好了,我們能夠直接添加我們想要的功能,在這美麗的日子里,cxuan 添加了用戶代理、日志代理等等無數(shù)個代理類。但是好景不長,cxuan 發(fā)現(xiàn)每次改代碼的時候都要改每個代理類,這就很煩啊!我寶貴的時光都浪費在改每個代理類上面了嗎?

          動態(tài)代理

          JDK 動態(tài)代理

          于是乎 cxuan 上網(wǎng)求助,發(fā)現(xiàn)了一個叫做動態(tài)代理的概念,通讀了一下,發(fā)現(xiàn)有點意思,于是乎 cxuan 修改了一下靜態(tài)代理的代碼,新增了一個?UserHandler?的用戶代理,并做了一下?test,代碼如下

          public?class?UserHandler?implements?InvocationHandler?{

          ????private?UserDao?userDao;

          ????public?UserHandler(UserDao?userDao){
          ????????this.userDao?=?userDao;
          ????}

          ????@Override
          ????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
          ????????saveUserStart();
          ????????Object?obj?=?method.invoke(userDao,?args);
          ????????saveUserDone();
          ????????return?obj;
          ????}

          ????public?void?saveUserStart(){
          ????????System.out.println("----?開始插入?----");
          ????}

          ????public?void?saveUserDone(){
          ????????System.out.println("----?插入完成?----");
          ????}
          }

          測試類如下

          public?static?void?dynamicProxy(){

          ??UserDao?userDao?=?new?UserDaoImpl();
          ??InvocationHandler?handler?=?new?UserHandler(userDao);

          ??ClassLoader?loader?=?userDao.getClass().getClassLoader();
          ??Class[]?interfaces?=?userDao.getClass().getInterfaces();

          ??UserDao?proxy?=?(UserDao)Proxy.newProxyInstance(loader,?interfaces,?handler);
          ??proxy.saveUser();
          }

          UserHandler?是用戶代理類,構造函數(shù)中的 UserDao 是真實對象,通過把 UserDao 隱藏進 UserHandler ,通過 UserHandler 中的 UserDao 執(zhí)行真正的方法。

          類加載器、接口數(shù)組你可以把它理解為一個方法樹,每棵葉子結點都是一個方法,通過后面的 proxy.saveUser() 來告訴 JVM 執(zhí)行的是方法樹上的哪個方法。

          用戶代理是通過類加載器、接口數(shù)組、代理類來得到的。saveUser 方法就相當于是告訴 proxy 你最終要執(zhí)行的是哪個方法,這個 proxy.saveUser 方法并不是最終直接執(zhí)行的 saveUser 方法,最終的 saveUser 方法是由 UserHandler 中的 invoke 方法觸發(fā)的。

          上面這種在編譯期無法確定最終的執(zhí)行方法,而只能通過運行時動態(tài)獲取方法的代理模式被稱為?動態(tài)代理

          動態(tài)代理的優(yōu)勢是實現(xiàn)無侵入式的代碼擴展,也可以對方法進行增強。此外,也可以大大減少代碼量,避免代理類泛濫成災的情況。

          所以我們現(xiàn)在總結一下靜態(tài)代理和動態(tài)代理各自的特點。

          靜態(tài)代理

          • 靜態(tài)代理類:由程序員創(chuàng)建或者由第三方工具生成,再進行編譯;在程序運行之前,代理類的 .class 文件已經(jīng)存在了。

          • 靜態(tài)代理事先知道要代理的是什么。

          • 靜態(tài)代理類通常只代理一個類。

          動態(tài)代理

          • 動態(tài)代理通常是在程序運行時,通過反射機制動態(tài)生成的。

          • 動態(tài)代理類通常代理接口下的所有類。

          • 動態(tài)代理事先不知道要代理的是什么,只有在運行的時候才能確定。

          • 動態(tài)代理的調用處理程序必須事先繼承 InvocationHandler 接口,使用 Proxy 類中的 newProxyInstance 方法動態(tài)的創(chuàng)建代理類。

          在上面的代碼示例中,我們是定義了一個 UserDao 接口,然后有 UserDaoImpl 接口的實現(xiàn)類,我們通過 Proxy.newProxyInstance 方法得到的也是 UserDao 的實現(xiàn)類對象,那么其實這是一種基于接口的動態(tài)代理。也叫做?JDK 動態(tài)代理

          是不是只有這一種動態(tài)代理技術呢?既然都這么問了,那當然不是。

          除此之外,還有一些其他代理技術,不過是需要加載額外的 jar 包的,那么我們匯總一下所有的代理技術和它的特征

          • JDK 的動態(tài)代理使用簡單,它內置在 JDK 中,因此不需要引入第三方 Jar 包。

          • CGLIB 和 Javassist 都是高級的字節(jié)碼生成庫,總體性能比 JDK 自帶的動態(tài)代理好,而且功能十分強大。

          • ASM 是低級的字節(jié)碼生成工具,使用 ASM 已經(jīng)近乎于在使用字節(jié)碼編程,對開發(fā)人員要求最高。當然,也是性能最好的一種動態(tài)代理生成工具。但 ASM 的使用很繁瑣,而且性能也沒有數(shù)量級的提升,與 CGLIB 等高級字節(jié)碼生成工具相比,ASM 程序的維護性較差,如果不是在對性能有苛刻要求的場合,還是推薦 CGLIB 或者 Javassist。

          下面我們就來依次介紹一下這些動態(tài)代理工具的使用

          CGLIB 動態(tài)代理

          上面我們提到 JDK 動態(tài)代理是基于接口的代理,而 CGLIB 動態(tài)代理是針對類實現(xiàn)代理,主要是對指定的類生成一個子類,覆蓋其中的方法?,也就是說 CGLIB 動態(tài)代理采用類繼承 -> 方法重寫的方式進行的,下面我們先來看一下 CGLIB 動態(tài)代理的結構。

          如上圖所示,代理類繼承于目標類,每次調用代理類的方法都會在攔截器中進行攔截,攔截器中再會調用目標類的方法。

          下面我們通過一個示例來演示一下 CGLIB 動態(tài)代理的使用

          首先導入 CGLIB 相關 jar 包,我們使用的是 MAVEN 的方式

          <dependency>
          ??<groupId>cglibgroupId>
          ??<artifactId>cglibartifactId>
          ??<version>3.2.5version>
          dependency>

          然后我們新創(chuàng)建一個 UserService 類,為了和上面的 UserDao 和 UserDaoImpl 進行區(qū)分。

          public?class?UserService?{
          ???public?void?saveUser(){
          ???????System.out.println("----?保存用戶?----");
          ???}
          }

          之后我們創(chuàng)建一個自定義方法攔截器,這個自定義方法攔截器實現(xiàn)了攔截器類

          public?class?AutoMethodInterceptor?implements?MethodInterceptor?{

          ????public?Object?intercept(Object?obj,?Method?method,?Object[]?args,?MethodProxy?methodProxy)?throws?Throwable?{
          ????????System.out.println("----?方法攔截?----");
          ????????Object?object?=?methodProxy.invokeSuper(obj,?args);
          ????????return?object;
          ????}
          }

          這里解釋一下這幾個參數(shù)都是什么含義

          • Object obj: obj 是 CGLIB 動態(tài)生成代理類實例

          • Method method: Method 為實體類所調用的被代理的方法引用

          • Objectp[] args: 這個就是方法的參數(shù)列表

          • MethodProxy methodProxy : 這個就是生成的代理類對方法的引用。

          對于?methodProxy?參數(shù)調用的方法,在其內部有兩種選擇:invoke()?和?invokeSuper()?,二者的區(qū)別不在本文展開說明,感興趣的讀者可以參考本篇文章:Cglib源碼分析 invoke和invokeSuper的差別

          然后我們創(chuàng)建一個測試類進行測試

          public?static?void?main(String[]?args)?{

          ??Enhancer?enhancer?=?new?Enhancer();
          ??enhancer.setSuperclass(UserService.class);
          ??enhancer.setCallback(new?AutoMethodInterceptor());

          ??UserService?userService?=?(UserService)enhancer.create();

          ??userService.saveUser();
          }

          測試類主要涉及?Enhancer?的使用,Enhancer 是一個非常重要的類,它允許為非接口類型創(chuàng)建一個 Java 代理,Enhancer 動態(tài)的創(chuàng)建給定類的子類并且攔截代理類的所有的方法,和 JDK 動態(tài)代理不一樣的是不管是接口還是類它都能正常工作。

          JDK 動態(tài)代理與 CGLIB 動態(tài)代理都是將真實對象隱藏在代理對象的后面,以達到?代理?的效果。與 JDK 動態(tài)代理所不同的是 CGLIB 動態(tài)代理使用 Enhancer 來創(chuàng)建代理對象,而 JDK 動態(tài)代理使用的是 Proxy.newProxyInstance 來創(chuàng)建代理對象;還有一點是 CGLIB 可以代理大部分類,而 JDK 動態(tài)代理只能代理實現(xiàn)了接口的類。

          Javassist 代理

          Javassist是在 Java 中編輯字節(jié)碼的類庫;它使 Java 程序能夠在運行時定義一個新類, 并在 JVM 加載時修改類文件。我們使用最頻繁的動態(tài)特性就是?反射,而且反射也是動態(tài)代理的基礎,我們之所以沒有提反射對動態(tài)代理的作用是因為我想在后面詳聊,反射可以在運行時查找對象屬性、方法,修改作用域,通過方法名稱調用方法等。實時應用不會頻繁使用反射來創(chuàng)建,因為反射開銷比較大,另外,還有一種具有和反射一樣功能強大的特性那就是?Javaassist

          我們先通過一個簡單的示例來演示一下 Javaassist ,以及 Javaassist 如何創(chuàng)建動態(tài)代理。

          我們仍舊使用上面提到的 UserDao 和 UserDaoImpl 作為基類。

          我們新創(chuàng)建一個 AssistByteCode 類,它里面有一個 createByteCode 方法,這個方法主要做的事情就是通過字節(jié)碼生成 UserDaoImpl 實現(xiàn)類。我們下面來看一下它的代碼

          public?class?AssistByteCode?{

          ????public?static?void?createByteCode()?throws?Exception{
          ????????ClassPool?classPool?=?ClassPool.getDefault();
          ????????CtClass?cc?=?classPool.makeClass("com.cxuan.proxypattern.UserDaoImpl");

          ????????//?設置接口
          ????????CtClass?ctClass?=?classPool.get("com.cxuan.proxypattern.UserDao");
          ????????cc.setInterfaces(new?CtClass[]?{ctClass});

          ????????//?創(chuàng)建方法
          ????????CtMethod?saveUser?=?CtMethod.make("public?void?saveUser(){}",?cc);
          ????????saveUser.setBody("System.out.println(\"----?插入用戶?----\");");
          ????????cc.addMethod(saveUser);

          ????????Class?c?=?cc.toClass();
          ????????cc.writeFile("/Users/mr.l/cxuan-justdoit");

          ????}
          }

          由于本文并不是一個具體研究 Javaassist 的文章,所以我們不會過多研究細節(jié)問題,只專注于這個框架一些比較重要的類

          ClassPool:ClassPool 就是一個 CtClass 的容器,而一個?CtClass?對象就是一個 class 對象的實例,這個實例和 class 對象一樣,包含屬性、方法等。

          那么上面代碼主要做了哪些事兒呢?通過 ClassPool 來獲取 CtClass 所需要的接口、抽象類的 CtClass 實例,然后通過 CtClass 實例添加自己的屬性和方法,并通過它的 writeFile 把二進制流輸出到當前項目的根目錄路徑下。writeFile 其內部是使用了?DataOutputStream?進行輸出的。

          流寫完后,我們打開這個?.class?文件如下所示


          public?class?UserDaoImpl?implements?UserDao?{
          ????public?void?saveUser()?{
          ????????System.out.println("----?插入用戶?----");
          ????}

          ????public?UserDaoImpl()?{
          ????}
          }

          可以對比一下上面發(fā)現(xiàn) UserDaoImpl 發(fā)現(xiàn)編譯器除了為我們添加了一個公有的構造器,其他基本一致。

          經(jīng)過這個簡單的示例后,cxuan 給你演示一下如何使用 Javaassist 動態(tài)代理。

          首先我們先創(chuàng)建一個 Javaassist 的代理工廠,代碼如下

          public?class?JavaassistProxyFactory?{

          ????public?Object?getProxy(Class?clazz)?throws?Exception{

          ????????//?代理工廠
          ????????ProxyFactory?proxyFactory?=?new?ProxyFactory();
          ????????//?設置需要創(chuàng)建的子類
          ????????proxyFactory.setSuperclass(clazz);
          ????????proxyFactory.setHandler((self,?thisMethod,?proceed,?args)?->?{

          ????????????System.out.println("----?開始攔截?----");
          ????????????Object?result?=?proceed.invoke(self,?args);
          ????????????System.out.println("----?結束攔截?----");

          ????????????return?result;
          ????????});
          ????????return?proxyFactory.createClass().newInstance();

          ????}
          }

          上面我們定義了一個代理工廠,代理工廠里面創(chuàng)建了一個 handler,在調用目標方法時,Javassist 會回調 MethodHandler 接口方法攔截,來調用真正執(zhí)行的方法,你可以在攔截方法的前后實現(xiàn)自己的業(yè)務邏輯。最后的?proxyFactory.createClass().newInstance()?就是使用字節(jié)碼技術來創(chuàng)建了最終的子類實例,這種代理方式類似于 JDK 中的 InvocationHandler 接口。

          測試方法如下

          public?static?void?main(String[]?args)?throws?Exception?{

          ??JavaassistProxyFactory?proxyFactory?=?new?JavaassistProxyFactory();
          ??UserService?userProxy?=?(UserService)?proxyFactory.getProxy(UserService.class);
          ??userProxy.saveUser();
          }

          ASM 代理

          ASM 是一套 Java 字節(jié)碼生成架構,它可以動態(tài)生成二進制格式的子類或其它代理類,或者在類被 Java 虛擬機裝入內存之前,動態(tài)修改類。

          下面我們使用 ASM 框架實現(xiàn)一個動態(tài)代理,ASM 生成的動態(tài)代理

          以下代碼摘自 https://blog.csdn.net/lightj1996/article/details/107305662

          public?class?AsmProxy?extends?ClassLoader?implements?Opcodes?{

          ????public?static?void?createAsmProxy()?throws?Exception?{

          ????????//?目標類類名?字節(jié)碼中類修飾符以?“/”?分割
          ????????String?targetServiceName?=?TargetService.class.getName().replace(".",?"/");
          ????????//?切面類類名
          ????????String?aspectServiceName?=?AspectService.class.getName().replace(".",?"/");
          ????????//?代理類類名
          ????????String?proxyServiceName?=?targetServiceName+"Proxy";
          ????????//?創(chuàng)建一個?classWriter?它是繼承了ClassVisitor
          ????????ClassWriter?classWriter?=?new?ClassWriter(0);
          ????????//?訪問類?指定jdk版本號為1.8,?修飾符為?public,父類是TargetService
          ????????classWriter.visit(Opcodes.V1_8,?Opcodes.ACC_PUBLIC,?proxyServiceName,?null,?targetServiceName,?null);
          ????????//?訪問目標類成員變量?為類添加切面屬性?“private?TargetService?targetService”
          ????????classWriter.visitField(ACC_PRIVATE,?"targetService",?"L"?+?targetServiceName+";",?null,?null);
          ????????//?訪問切面類成員變量?為類添加目標屬性?“private?AspectService?aspectService”
          ????????classWriter.visitField(ACC_PRIVATE,?"aspectService",?"L"?+?aspectServiceName+";",?null,?null);

          ????????//?訪問默認構造方法?TargetServiceProxy()
          ????????//?定義函數(shù)?修飾符為public?方法名為?,?方法表述符為()V?表示無參數(shù),無返回參數(shù)
          ????????MethodVisitor?initVisitor?=?classWriter.visitMethod(ACC_PUBLIC,?"",?"()V",?null,?null);
          ????????//?從局部變量表取第0個元素?“this”
          ????????initVisitor.visitVarInsn(ALOAD,?0);
          ????????//?調用super?的構造方法?invokeSpecial在這里的意思是調用父類方法
          ????????initVisitor.visitMethodInsn(INVOKESPECIAL,?targetServiceName,?"",?"()V",?false);
          ????????//?方法返回
          ????????initVisitor.visitInsn(RETURN);
          ????????//?設置最大棧數(shù)量,最大局部變量表數(shù)量
          ????????initVisitor.visitMaxs(1,?1);
          ????????//?訪問結束
          ????????initVisitor.visitEnd();

          ????????//?創(chuàng)建有參構造方法?TargetServiceProxy(TargetService?var1,?AspectService?var2)
          ????????//?定義函數(shù)?修飾符為public?方法名為?,?方法表述符為(TargetService,?AspectService)V?表示無參數(shù),無返回參數(shù)
          ????????MethodVisitor?methodVisitor?=?classWriter.visitMethod(ACC_PUBLIC,?"",?"(L"?+?targetServiceName?+?";L"+aspectServiceName+";)V",?null,?null);
          ????????//?從局部變量表取第0個元素?“this”壓入棧頂
          ????????methodVisitor.visitVarInsn(ALOAD,?0);
          ????????// this出棧 , 調用super 的構造方法 invokeSpecial在這里的意思是調用父類方法。?的owner是AspectService, 無參無返回類型
          ????????methodVisitor.visitMethodInsn(INVOKESPECIAL,?targetServiceName,?"",?"()V",?false);
          ????????//?從局部變量表取第0個元素?“this”壓入棧頂
          ????????methodVisitor.visitVarInsn(ALOAD,?0);
          ????????//?從局部變量表取第1個元素?“targetService”壓入棧頂
          ????????methodVisitor.visitVarInsn(ALOAD,?1);
          ????????//?this?和?targetService?出棧,?調用targetService?put?賦值給this.targetService
          ????????methodVisitor.visitFieldInsn(PUTFIELD,?proxyServiceName,?"targetService",?"L"?+?targetServiceName?+?";");
          ????????//?從局部變量表取第0個元素?“this”壓入棧頂
          ????????methodVisitor.visitVarInsn(ALOAD,?0);
          ????????//?從局部變量表取第2個元素?“aspectService”壓入棧頂
          ????????methodVisitor.visitVarInsn(ALOAD,?2);
          ????????//?this?和?aspectService?出棧?將?targetService?put?賦值給this.aspectService
          ????????methodVisitor.visitFieldInsn(PUTFIELD,?proxyServiceName,?"aspectService",?"L"?+?aspectServiceName?+?";");
          ????????//?方法返回
          ????????methodVisitor.visitInsn(RETURN);
          ????????//?設置最大棧數(shù)量,最大局部變量表數(shù)量
          ????????methodVisitor.visitMaxs(2,?3);
          ????????//?方法返回
          ????????methodVisitor.visitEnd();

          ????????//?創(chuàng)建代理方法?修飾符為public,方法名為?demoQuest
          ????????MethodVisitor?visitMethod?=?classWriter.visitMethod(ACC_PUBLIC,?"demoQuest",?"()I",?null,?null);
          ????????//?從局部變量表取第0個元素?“this”壓入棧頂
          ????????visitMethod.visitVarInsn(ALOAD,?0);
          ????????//?this?出棧?將this.aspectService壓入棧頂
          ????????visitMethod.visitFieldInsn(GETFIELD,?proxyServiceName,?"aspectService",?"L"+aspectServiceName+";");
          ????????//?取棧頂元素出棧?也就是targetService?調用其preOperation方法,?demoQuest的owner是AspectService,?無參無返回類型
          ????????visitMethod.visitMethodInsn(INVOKEVIRTUAL,?aspectServiceName,"preOperation",?"()V",?false);
          ????????//?從局部變量表取第0個元素?“this”壓入棧頂
          ????????visitMethod.visitVarInsn(ALOAD,?0);
          ????????//?this?出棧,?取this.targetService壓入棧頂
          ????????visitMethod.visitFieldInsn(GETFIELD,?proxyServiceName,?"targetService",?"L"+targetServiceName+";");
          ????????//?取棧頂元素出棧?也就是targetService調用其demoQuest方法,?demoQuest的owner是TargetService,?無參無返回類型
          ????????visitMethod.visitMethodInsn(INVOKEVIRTUAL,?targetServiceName,?"demoQuest",?"()I",?false);
          ????????//?方法返回
          ????????visitMethod.visitInsn(IRETURN);
          ????????//?設置最大棧數(shù)量,最大局部變量表數(shù)量
          ????????visitMethod.visitMaxs(1,?1);
          ????????//?方法返回
          ????????visitMethod.visitEnd();

          ????????//?生成字節(jié)碼二進制流
          ????????byte[]?code?=?classWriter.toByteArray();
          ????????//?自定義classloader加載類
          ????????Class?clazz?=?(new?AsmProxy()).defineClass(TargetService.class.getName()?+?"Proxy",?code,?0,?code.length);
          ????????//?取其帶參數(shù)的構造方法
          ????????Constructor?constructor?=?clazz.getConstructor(TargetService.class,?AspectService.class);
          ????????//?使用構造方法實例化對象
          ????????Object?object?=?constructor.newInstance(new?TargetService(),?new?AspectService());

          ????????//?使用TargetService類型的引用接收這個對象
          ????????TargetService?targetService;
          ????????if?(!(object?instanceof?TargetService))?{
          ????????????return;
          ????????}
          ????????targetService?=?(TargetService)object;

          ????????System.out.println("生成代理類的名稱:?"?+?targetService.getClass().getName());
          ????????//?調用被代理方法
          ????????targetService.demoQuest();

          ????????//?這里可以不用寫,?但是如果想看最后生成的字節(jié)碼長什么樣子,可以寫?"ascp-purchase-app/target/classes/"是我的根目錄,?閱讀者需要將其替換成自己的
          ????????String?classPath?=?"/Users/mr.l/cxuan-justdoit/";
          ????????String?path?=?classPath?+?proxyServiceName?+?".class";
          ????????FileOutputStream?fos?=
          ????????????????new?FileOutputStream(path);
          ????????fos.write(code);
          ????????fos.close();

          ????}
          }

          使用 ASM 生成動態(tài)代理的代碼比較長,上面這段代碼的含義就是生成類 TargetServiceProxy,用于代理TargetService ,在調用 targetService.demoQuest() 方法之前調用切面的方法 aspectService.preOperation();

          測試類就直接調用 AsmProxy.createAsmProxy() 方法即可,比較簡單。

          下面是我們生成 TargetServiceProxy 的目標類

          至此,我們已經(jīng)介紹了四種動態(tài)代理的方式,分別是JDK 動態(tài)代理、CGLIB 動態(tài)代理、Javaassist 動態(tài)代理、ASM 動態(tài)代理,那么現(xiàn)在思考一個問題,為什么會有動態(tài)代理的出現(xiàn)呢?或者說動態(tài)代理是基于什么原理呢?

          其實我們上面已經(jīng)提到過了,沒錯,動態(tài)代理使用的就是反射?機制,反射機制是 Java 語言提供的一種基礎功能,????賦予程序在運行時動態(tài)修改屬性、方法的能力。通過反射我們能夠直接操作類或者對象,比如獲取某個類的定義,獲取某個類的屬性和方法等。

          —?【 THE END 】—
          本公眾號全部博文已整理成一個目錄,請在公眾號里回復「m」獲取!


          3T技術資源大放送!包括但不限于:Java、C/C++,Linux,Python,大數(shù)據(jù),人工智能等等。在公眾號內回復「1024」,即可免費獲取!!




          瀏覽 45
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  奇米影视久久久 | 网站18禁看黄 | 精品多人P群无码专区 | 青青操久操视频 | 亚洲精品国产精品久久99热 |