溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

什么是動(dòng)態(tài)代理模式

發(fā)布時(shí)間:2021-10-21 10:25:06 來(lái)源:億速云 閱讀:223 作者:iii 欄目:web開(kāi)發(fā)

這篇文章主要講解了“什么是動(dòng)態(tài)代理模式”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“什么是動(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ò)展該方法。

什么是動(dòng)態(tài)代理模式

如上圖所示,用戶(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)忙其他事情了。如下圖所示

什么是動(dòng)態(tà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>

什么是動(dòng)態(tài)代理模式

此時(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)代理,我們先演示一下動(dòng)態(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è)黾邮裁葱鹿δ艿脑?,我們可以直接增加一個(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)。

什么是動(dòng)態(tài)代理模式

如上圖所示,代理類(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>cglib</groupId>   <artifactId>cglib</artifactId>   <version>3.2.5</version> </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 文件如下所示

什么是動(dòng)態(tài)代理模式

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)造器,其他基本一致。

什么是動(dòng)態(tài)代理模式

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

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

public class JavaassistProxyFactory {      public Object getProxy(Class clazz) throws Exception{          // 代理工廠         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è)代理工廠,代理工廠里面創(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);         // 訪問(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);         // 訪問(wèn)目標(biāo)類(lèi)成員變量 為類(lèi)添加切面屬性 “private TargetService targetService”         classWriter.visitField(ACC_PRIVATE, "targetService", "L" + targetServiceName+";", null, null);         // 訪問(wèn)切面類(lèi)成員變量 為類(lèi)添加目標(biāo)屬性 “private AspectService aspectService”         classWriter.visitField(ACC_PRIVATE, "aspectService", "L" + aspectServiceName+";", null, null);          // 訪問(wèn)默認(rèn)構(gòu)造方法 TargetServiceProxy()         // 定義函數(shù) 修飾符為public 方法名為 <init>, 方法表述符為()V 表示無(wú)參數(shù),無(wú)返回參數(shù)         MethodVisitor initVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);         // 從局部變量表取第0個(gè)元素 “this”         initVisitor.visitVarInsn(ALOAD, 0);         // 調(diào)用super 的構(gòu)造方法 invokeSpecial在這里的意思是調(diào)用父類(lèi)方法         initVisitor.visitMethodInsn(INVOKESPECIAL, targetServiceName, "<init>", "()V", false);         // 方法返回         initVisitor.visitInsn(RETURN);         // 設(shè)置最大棧數(shù)量,最大局部變量表數(shù)量         initVisitor.visitMaxs(1, 1);         // 訪問(wèn)結(jié)束         initVisitor.visitEnd();          // 創(chuàng)建有參構(gòu)造方法 TargetServiceProxy(TargetService var1, AspectService var2)         // 定義函數(shù) 修飾符為public 方法名為 <init>, 方法表述符為(TargetService, AspectService)V 表示無(wú)參數(shù),無(wú)返回參數(shù)         MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "(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)方法。 <init>的owner是AspectService, 無(wú)參無(wú)返回類(lèi)型         methodVisitor.visitMethodInsn(INVOKESPECIAL, targetServiceName, "<init>", "()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)

什么是動(dòng)態(tà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ù)性、編碼工作量同等重要。

感謝各位的閱讀,以上就是“什么是動(dòng)態(tài)代理模式”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)什么是動(dòng)態(tài)代理模式這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

jdk
AI