<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>

          面試官:請(qǐng)問(wèn)動(dòng)態(tài)代理是怎么實(shí)現(xiàn)的?

          共 15181字,需瀏覽 31分鐘

           ·

          2021-01-03 20:46

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

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



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

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

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

          代理模式

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

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

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

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

          代理其實(shí)就和經(jīng)紀(jì)人一樣,比如你是一個(gè)明星,有很多粉絲。你的流量很多,經(jīng)常會(huì)有很多金主來(lái)找你洽談合作等,你自己肯定忙不過(guò)來(lái),因?yàn)槟阋幚淼牟恢皇钦労献鬟@件事情,你還要懂才藝、拍戲、維護(hù)和粉絲的關(guān)系、營(yíng)銷(xiāo)等。為此,你找了一個(gè)經(jīng)紀(jì)人,你讓他負(fù)責(zé)和金主談合作這件事,經(jīng)紀(jì)人做事很認(rèn)真負(fù)責(zé),圓滿(mǎn)的完成了任務(wù),于是,金主找你談合作就變成了金主和你的經(jīng)紀(jì)人談合作,你就有更多的時(shí)間來(lái)忙其他事情了。如下圖所示

          這是一種靜態(tài)代理,因?yàn)檫@個(gè)代理(經(jīng)紀(jì)人)是你自己親自挑選的。

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

          此時(shí)你幾乎所有的工作都是由代理公司來(lái)進(jìn)行打理,而他們派出誰(shuí)來(lái)幫你做這些事情你就不得而知了,這得根據(jù)實(shí)際情況來(lái)定,因?yàn)榇砉疽膊恢皇秦?fù)責(zé)你一個(gè)明星,而且每個(gè)人所擅長(zhǎng)的領(lǐng)域也不同,所以你只有等到有實(shí)際需求后,才會(huì)給你指定對(duì)應(yīng)的代理人,這種情況就叫做動(dòng)態(tài)代理

          靜態(tài)代理

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

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

          public?interface?UserDao?{

          ????void?saveUser();
          }

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

          public?class?UserDaoImpl?implements?UserDao{

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

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

          public?class?UserProxy?{

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

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

          下面是測(cè)試方法。

          public?class?UserTest?{

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

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

          ????}
          }

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

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

          動(dòng)態(tài)代理

          JDK 動(dòng)態(tài)代理

          于是乎 cxuan 上網(wǎng)求助,發(fā)現(xiàn)了一個(gè)叫做動(dòng)態(tài)代理的概念,通讀了一下,發(fā)現(xiàn)有點(diǎn)意思,于是乎 cxuan 修改了一下靜態(tài)代理的代碼,新增了一個(gè)?UserHandler?的用戶(hù)代理,并做了一下?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("----?開(kāi)始插入?----");
          ????}

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

          測(cè)試類(lèi)如下

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

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

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

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

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

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

          靜態(tài)代理

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

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

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

          動(dòng)態(tài)代理

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

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

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

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

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

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

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

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

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

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

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

          CGLIB 動(dòng)態(tài)代理

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

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

          下面我們通過(guò)一個(gè)示例來(lái)演示一下 CGLIB 動(dòng)態(tài)代理的使用

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

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

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

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

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

          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;
          ????}
          }

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

          • Object obj: obj 是 CGLIB 動(dòng)態(tài)生成代理類(lèi)實(shí)例

          • Method method: Method 為實(shí)體類(lèi)所調(diào)用的被代理的方法引用

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

          • MethodProxy methodProxy : 這個(gè)就是生成的代理類(lèi)對(duì)方法的引用。

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

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

          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();
          }

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

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

          Javassist 代理

          Javassist是在 Java 中編輯字節(jié)碼的類(lèi)庫(kù);它使 Java 程序能夠在運(yùn)行時(shí)定義一個(gè)新類(lèi), 并在 JVM 加載時(shí)修改類(lèi)文件。我們使用最頻繁的動(dòng)態(tài)特性就是?反射,而且反射也是動(dòng)態(tài)代理的基礎(chǔ),我們之所以沒(méi)有提反射對(duì)動(dòng)態(tài)代理的作用是因?yàn)槲蚁朐诤竺嬖斄模瓷淇梢栽谶\(yùn)行時(shí)查找對(duì)象屬性、方法,修改作用域,通過(guò)方法名稱(chēng)調(diào)用方法等。實(shí)時(shí)應(yīng)用不會(huì)頻繁使用反射來(lái)創(chuàng)建,因?yàn)榉瓷溟_(kāi)銷(xiāo)比較大,另外,還有一種具有和反射一樣功能強(qiáng)大的特性那就是?Javaassist

          我們先通過(guò)一個(gè)簡(jiǎn)單的示例來(lái)演示一下 Javaassist ,以及 Javaassist 如何創(chuàng)建動(dòng)態(tài)代理。

          我們?nèi)耘f使用上面提到的 UserDao 和 UserDaoImpl 作為基類(lèi)。

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

          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(\"----?插入用戶(hù)?----\");");
          ????????cc.addMethod(saveUser);

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

          ????}
          }

          由于本文并不是一個(gè)具體研究 Javaassist 的文章,所以我們不會(huì)過(guò)多研究細(xì)節(jié)問(wèn)題,只專(zhuān)注于這個(gè)框架一些比較重要的類(lèi)

          ClassPool:ClassPool 就是一個(gè) CtClass 的容器,而一個(gè)?CtClass?對(duì)象就是一個(gè) class 對(duì)象的實(shí)例,這個(gè)實(shí)例和 class 對(duì)象一樣,包含屬性、方法等。

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

          流寫(xiě)完后,我們打開(kāi)這個(gè)?.class?文件如下所示


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

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

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

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

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

          public?class?JavaassistProxyFactory?{

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

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

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

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

          ????}
          }

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

          測(cè)試方法如下

          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),它可以動(dòng)態(tài)生成二進(jìn)制格式的子類(lèi)或其它代理類(lèi),或者在類(lèi)被 Java 虛擬機(jī)裝入內(nèi)存之前,動(dòng)態(tài)修改類(lèi)。

          下面我們使用 ASM 框架實(shí)現(xiàn)一個(gè)動(dòng)態(tài)代理,ASM 生成的動(dòng)態(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)類(lèi)類(lèi)名?字節(jié)碼中類(lèi)修飾符以?“/”?分割
          ????????String?targetServiceName?=?TargetService.class.getName().replace(".",?"/");
          ????????//?切面類(lèi)類(lèi)名
          ????????String?aspectServiceName?=?AspectService.class.getName().replace(".",?"/");
          ????????//?代理類(lèi)類(lèi)名
          ????????String?proxyServiceName?=?targetServiceName+"Proxy";
          ????????//?創(chuàng)建一個(gè)?classWriter?它是繼承了ClassVisitor
          ????????ClassWriter?classWriter?=?new?ClassWriter(0);
          ????????//?訪(fǎng)問(wèn)類(lèi)?指定jdk版本號(hào)為1.8,?修飾符為?public,父類(lèi)是TargetService
          ????????classWriter.visit(Opcodes.V1_8,?Opcodes.ACC_PUBLIC,?proxyServiceName,?null,?targetServiceName,?null);
          ????????//?訪(fǎng)問(wèn)目標(biāo)類(lèi)成員變量?為類(lèi)添加切面屬性?“private?TargetService?targetService”
          ????????classWriter.visitField(ACC_PRIVATE,?"targetService",?"L"?+?targetServiceName+";",?null,?null);
          ????????//?訪(fǎng)問(wèn)切面類(lèi)成員變量?為類(lèi)添加目標(biāo)屬性?“private?AspectService?aspectService”
          ????????classWriter.visitField(ACC_PRIVATE,?"aspectService",?"L"?+?aspectServiceName+";",?null,?null);

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

          ????????//?創(chuàng)建有參構(gòu)造方法?TargetServiceProxy(TargetService?var1,?AspectService?var2)
          ????????//?定義函數(shù)?修飾符為public?方法名為?,?方法表述符為(TargetService,?AspectService)V?表示無(wú)參數(shù),無(wú)返回參數(shù)
          ????????MethodVisitor?methodVisitor?=?classWriter.visitMethod(ACC_PUBLIC,?"",?"(L"?+?targetServiceName?+?";L"+aspectServiceName+";)V",?null,?null);
          ????????//?從局部變量表取第0個(gè)元素?“this”壓入棧頂
          ????????methodVisitor.visitVarInsn(ALOAD,?0);
          ????????// this出棧 , 調(diào)用super 的構(gòu)造方法 invokeSpecial在這里的意思是調(diào)用父類(lèi)方法。?的owner是AspectService, 無(wú)參無(wú)返回類(lèi)型
          ????????methodVisitor.visitMethodInsn(INVOKESPECIAL,?targetServiceName,?"",?"()V",?false);
          ????????//?從局部變量表取第0個(gè)元素?“this”壓入棧頂
          ????????methodVisitor.visitVarInsn(ALOAD,?0);
          ????????//?從局部變量表取第1個(gè)元素?“targetService”壓入棧頂
          ????????methodVisitor.visitVarInsn(ALOAD,?1);
          ????????//?this?和?targetService?出棧,?調(diào)用targetService?put?賦值給this.targetService
          ????????methodVisitor.visitFieldInsn(PUTFIELD,?proxyServiceName,?"targetService",?"L"?+?targetServiceName?+?";");
          ????????//?從局部變量表取第0個(gè)元素?“this”壓入棧頂
          ????????methodVisitor.visitVarInsn(ALOAD,?0);
          ????????//?從局部變量表取第2個(gè)元素?“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個(gè)元素?“this”壓入棧頂
          ????????visitMethod.visitVarInsn(ALOAD,?0);
          ????????//?this?出棧?將this.aspectService壓入棧頂
          ????????visitMethod.visitFieldInsn(GETFIELD,?proxyServiceName,?"aspectService",?"L"+aspectServiceName+";");
          ????????//?取棧頂元素出棧?也就是targetService?調(diào)用其preOperation方法,?demoQuest的owner是AspectService,?無(wú)參無(wú)返回類(lèi)型
          ????????visitMethod.visitMethodInsn(INVOKEVIRTUAL,?aspectServiceName,"preOperation",?"()V",?false);
          ????????//?從局部變量表取第0個(gè)元素?“this”壓入棧頂
          ????????visitMethod.visitVarInsn(ALOAD,?0);
          ????????//?this?出棧,?取this.targetService壓入棧頂
          ????????visitMethod.visitFieldInsn(GETFIELD,?proxyServiceName,?"targetService",?"L"+targetServiceName+";");
          ????????//?取棧頂元素出棧?也就是targetService調(diào)用其demoQuest方法,?demoQuest的owner是TargetService,?無(wú)參無(wú)返回類(lèi)型
          ????????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加載類(lèi)
          ????????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)造方法實(shí)例化對(duì)象
          ????????Object?object?=?constructor.newInstance(new?TargetService(),?new?AspectService());

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

          ????????System.out.println("生成代理類(lèi)的名稱(chēng):?"?+?targetService.getClass().getName());
          ????????//?調(diào)用被代理方法
          ????????targetService.demoQuest();

          ????????//?這里可以不用寫(xiě),?但是如果想看最后生成的字節(jié)碼長(zhǎng)什么樣子,可以寫(xiě)?"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 生成動(dòng)態(tài)代理的代碼比較長(zhǎng),上面這段代碼的含義就是生成類(lèi) TargetServiceProxy,用于代理TargetService ,在調(diào)用 targetService.demoQuest() 方法之前調(diào)用切面的方法 aspectService.preOperation();

          測(cè)試類(lèi)就直接調(diào)用 AsmProxy.createAsmProxy() 方法即可,比較簡(jiǎn)單。

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

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

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

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

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

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

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


          往期精選

          Caffeine !你簡(jiǎn)直太騷了!

          數(shù)據(jù)量大怎么搞?當(dāng)然是用這個(gè)了!

          為什么祖?zhèn)鞔a會(huì)被稱(chēng)為「屎山」?

          高贊回答:為什么高級(jí)程序員不必?fù)?dān)心自己的技術(shù)過(guò)時(shí)?

          Docker 從入門(mén)到干活,看這一篇足矣

          一文搞懂 CountDownLatch 用法和源碼!

          還敢亂寫(xiě)代碼??騰訊 Code Review 規(guī)范出爐!

          另外,cxuan 肝了六本 PDF,公號(hào)回復(fù) cxuan ,領(lǐng)取作者全部 PDF 。

          瀏覽 44
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  亚洲A片一区二区三区电影网 | 国产又爽 又黄 免费网站在线观看 | 熟妇乱伦网站 | 国产一区二区三区四区视频 | 亚洲AV成人电影 |