<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)代理竟然如此簡單!

          共 15064字,需瀏覽 31分鐘

           ·

          2020-12-31 11:37

          點擊藍(lán)色“Java建設(shè)者?”關(guān)注我喲

          加個“星標(biāo)”,及時閱讀最新技術(shù)文章


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


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

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

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

          代理模式

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

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

          如上圖所示,用戶不能直接使用目標(biāo)對象,而是構(gòu)造出一個代理對象,由代理對象作為中轉(zhuǎn),代理對象負(fù)責(zé)調(diào)用目標(biāo)對象真正的行為,從而把結(jié)果返回給用戶。

          也就是說,代理的關(guān)鍵點就是代理對象和目標(biāo)對象的關(guān)系

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

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

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

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

          靜態(tài)代理

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

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

          public?interface?UserDao?{

          ????void?saveUser();
          }

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

          public?class?UserDaoImpl?implements?UserDao{

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

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

          public?class?UserProxy?{

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

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

          下面是測試方法。

          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),其內(nèi)部會調(diào)用用戶實現(xiàn)類的保存用戶(saveUser)方法,因為我們 JVM 可以在編譯期確定最終的執(zhí)行方法,所以上面的這種代理模式又叫做靜態(tài)代理

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

          動態(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?是用戶代理類,構(gòu)造函數(shù)中的 UserDao 是真實對象,通過把 UserDao 隱藏進(jìn) UserHandler ,通過 UserHandler 中的 UserDao 執(zhí)行真正的方法。

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

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

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

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

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

          靜態(tài)代理

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

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

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

          動態(tài)代理

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

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

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

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

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

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

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

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

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

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

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

          CGLIB 動態(tài)代理

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

          如上圖所示,代理類繼承于目標(biāo)類,每次調(diào)用代理類的方法都會在攔截器中進(jìn)行攔截,攔截器中再會調(diào)用目標(biāo)類的方法。

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

          首先導(dǎo)入 CGLIB 相關(guān) jar 包,我們使用的是 MAVEN 的方式

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

          然后我們新創(chuàng)建一個 UserService 類,為了和上面的 UserDao 和 UserDaoImpl 進(jìn)行區(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 為實體類所調(diào)用的被代理的方法引用

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

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

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

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

          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)代理都是將真實對象隱藏在代理對象的后面,以達(dá)到?代理?的效果。與 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 程序能夠在運(yùn)行時定義一個新類, 并在 JVM 加載時修改類文件。我們使用最頻繁的動態(tài)特性就是?反射,而且反射也是動態(tài)代理的基礎(chǔ),我們之所以沒有提反射對動態(tài)代理的作用是因為我想在后面詳聊,反射可以在運(yùn)行時查找對象屬性、方法,修改作用域,通過方法名稱調(diào)用方法等。實時應(yīng)用不會頻繁使用反射來創(chuàng)建,因為反射開銷比較大,另外,還有一種具有和反射一樣功能強(qiáng)大的特性那就是?Javaassist

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

          我們?nèi)耘f使用上面提到的 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");

          ????????//?設(shè)置接口
          ????????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 的文章,所以我們不會過多研究細(xì)節(jié)問題,只專注于這個框架一些比較重要的類

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

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

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


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

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

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

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

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

          public?class?JavaassistProxyFactory?{

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

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

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

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

          ????}
          }

          上面我們定義了一個代理工廠,代理工廠里面創(chuàng)建了一個 handler,在調(diào)用目標(biāo)方法時,Javassist 會回調(diào) MethodHandler 接口方法攔截,來調(diào)用真正執(zhí)行的方法,你可以在攔截方法的前后實現(xiàn)自己的業(yè)務(wù)邏輯。最后的?proxyFactory.createClass().newInstance()?就是使用字節(jié)碼技術(shù)來創(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é)碼生成架構(gòu),它可以動態(tài)生成二進(jìn)制格式的子類或其它代理類,或者在類被 Java 虛擬機(jī)裝入內(nèi)存之前,動態(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?{

          ????????//?目標(biāo)類類名?字節(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);
          ????????//?訪問目標(biāo)類成員變量?為類添加切面屬性?“private?TargetService?targetService”
          ????????classWriter.visitField(ACC_PRIVATE,?"targetService",?"L"?+?targetServiceName+";",?null,?null);
          ????????//?訪問切面類成員變量?為類添加目標(biāo)屬性?“private?AspectService?aspectService”
          ????????classWriter.visitField(ACC_PRIVATE,?"aspectService",?"L"?+?aspectServiceName+";",?null,?null);

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

          ????????//?創(chuàng)建有參構(gòu)造方法?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出棧 , 調(diào)用super 的構(gòu)造方法 invokeSpecial在這里的意思是調(diào)用父類方法。?的owner是AspectService, 無參無返回類型
          ????????methodVisitor.visitMethodInsn(INVOKESPECIAL,?targetServiceName,?"",?"()V",?false);
          ????????//?從局部變量表取第0個元素?“this”壓入棧頂
          ????????methodVisitor.visitVarInsn(ALOAD,?0);
          ????????//?從局部變量表取第1個元素?“targetService”壓入棧頂
          ????????methodVisitor.visitVarInsn(ALOAD,?1);
          ????????//?this?和?targetService?出棧,?調(diào)用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ù)量,最大局部變量表數(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?調(diào)用其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調(diào)用其demoQuest方法,?demoQuest的owner是TargetService,?無參無返回類型
          ????????visitMethod.visitMethodInsn(INVOKEVIRTUAL,?targetServiceName,?"demoQuest",?"()I",?false);
          ????????//?方法返回
          ????????visitMethod.visitInsn(IRETURN);
          ????????//?設(shè)置最大棧數(shù)量,最大局部變量表數(shù)量
          ????????visitMethod.visitMaxs(1,?1);
          ????????//?方法返回
          ????????visitMethod.visitEnd();

          ????????//?生成字節(jié)碼二進(jìn)制流
          ????????byte[]?code?=?classWriter.toByteArray();
          ????????//?自定義classloader加載類
          ????????Class?clazz?=?(new?AsmProxy()).defineClass(TargetService.class.getName()?+?"Proxy",?code,?0,?code.length);
          ????????//?取其帶參數(shù)的構(gòu)造方法
          ????????Constructor?constructor?=?clazz.getConstructor(TargetService.class,?AspectService.class);
          ????????//?使用構(gòu)造方法實例化對象
          ????????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());
          ????????//?調(diào)用被代理方法
          ????????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 ,在調(diào)用 targetService.demoQuest() 方法之前調(diào)用切面的方法 aspectService.preOperation();

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

          下面是我們生成 TargetServiceProxy 的目標(biāo)類

          至此,我們已經(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)代理使用的就是反射?機(jī)制,反射機(jī)制是 Java 語言提供的一種基礎(chǔ)功能,??賦予程序在運(yùn)行時動態(tài)修改屬性、方法的能力。通過反射我們能夠直接操作類或者對象,比如獲取某個類的定義,獲取某個類的屬性和方法等。

          關(guān)于 Java 反射的相關(guān)內(nèi)容可以參考 Java建設(shè)者的這一篇文章

          給女同事講完代理后,女同事說:你好棒哦

          另外還有需要注意的一點,從性能角度來講,有些人得出結(jié)論說是 Java 動態(tài)代理要比 CGLIB 和 Javaassist 慢幾十倍,其實,在主流 JDK 版本中,Java 動態(tài)代理可以提供相等的性能水平,數(shù)量級的差距不是廣泛存在的。而且,在現(xiàn)代 JDK 中,反射已經(jīng)得到了改進(jìn)和優(yōu)化。

          我們在選型中,性能考量并不是主要關(guān)注點,可靠性、可維護(hù)性、編碼工作量同等重要。


          瀏覽 63
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  夜夜躁狠狠躁日日躁av | 大陆操屁屁视频在线观看 | 日韩淫乱无码视频播放 | 91麻豆精品国产91久久久久久久久 | 成人A级黄片 |