溫馨提示×

溫馨提示×

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

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

動(dòng)態(tài)代理知識(shí)總結(jié)

發(fā)布時(shí)間:2021-06-23 10:43:06 來源:億速云 閱讀:124 作者:chen 欄目:web開發(fā)

這篇文章主要講解了“動(dòng)態(tài)代理知識(shí)總結(jié)”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“動(dòng)態(tài)代理知識(shí)總結(jié)”吧!

代理模式

代理模式是一種設(shè)計(jì)模式,提供了對目標(biāo)對象額外的訪問方式,即通過代理對象訪問目標(biāo)對象,這樣可以在不修改原目標(biāo)對象的前提下,提供額外的功能操作,擴(kuò)展目標(biāo)對象的功能

一個(gè)比方:在租房的時(shí)候,有的人會(huì)通過房東直租,有的人會(huì)通過中介租房。

這兩種情況哪種比較方便呢?當(dāng)然是通過中介更加方便。

這里的中介就相當(dāng)于代理,用戶通過中介完成租房的一系列操作(看房、交押金、租房、清掃衛(wèi)生)代理模式可以有效的將具體的實(shí)現(xiàn)與調(diào)用方進(jìn)行解耦,通過面向接口進(jìn)行編碼完全將具體的實(shí)現(xiàn)隱藏在內(nèi)部。

動(dòng)態(tài)代理知識(shí)總結(jié)

分類:

靜態(tài)代理: 在編譯時(shí)就已經(jīng)實(shí)現(xiàn),編譯完成后代理類是一個(gè)實(shí)際的class文件

動(dòng)態(tài)代理: 在運(yùn)行時(shí)動(dòng)態(tài)生成的,即編譯完成后沒有實(shí)際的class文件,而是在運(yùn)行時(shí)動(dòng)態(tài)生成類字節(jié)碼,并加載到JVM中

靜態(tài)代理

使用方式

創(chuàng)建一個(gè)接口,然后創(chuàng)建被代理的類實(shí)現(xiàn)該接口并且實(shí)現(xiàn)該接口中的抽象方法。之后再創(chuàng)建一個(gè)代理類,同時(shí)使其也實(shí)現(xiàn)這個(gè)接口。在代理類中持有一個(gè)被代理對象的引用,而后在代理類方法中調(diào)用該對象的方法。

public interface UserDao {       void save();      }
public class UserDaoImpl implements UserDao {     @Override     public void save() {         System.out.println("正在保存用戶...");     } }
public class TransactionHandler implements UserDao {     //目標(biāo)代理對象     private UserDao target;     //構(gòu)造代理對象時(shí)傳入目標(biāo)對象     public TransactionHandler(UserDao target) {         this.target = target;     }     @Override     public void save() {         //調(diào)用目標(biāo)方法前的處理         System.out.println("開啟事務(wù)控制...");         //調(diào)用目標(biāo)對象的方法         target.save();         //調(diào)用目標(biāo)方法后的處理         System.out.println("關(guān)閉事務(wù)控制...");     } }
public class Main {     public static void main(String[] args) {         //新建目標(biāo)對象         UserDaoImpl target = new UserDaoImpl();         //創(chuàng)建代理對象, 并使用接口對其進(jìn)行引用         UserDao userDao = new TransactionHandler(target);         //針對接口進(jìn)行調(diào)用         userDao.save();     } }

使用JDK靜態(tài)代理很容易就完成了對一個(gè)類的代理操作。但是JDK靜態(tài)代理的缺點(diǎn)也暴露了出來:由于代理只能為一個(gè)類服務(wù),如果需要代理的類很多,那么就需要編寫大量的代理類,比較繁瑣

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

使用JDK動(dòng)態(tài)代理的五大步驟:

  1. 通過實(shí)現(xiàn)InvocationHandler接口來自定義自己的InvocationHandler;

  2. 通過Proxy.getProxyClass獲得動(dòng)態(tài)代理類;

  3. 通過反射機(jī)制獲得代理類的構(gòu)造方法,方法簽名為getConstructor(InvocationHandler.class);

  4. 通過構(gòu)造函數(shù)獲得代理對象并將自定義的InvocationHandler實(shí)例對象傳為參數(shù)傳入;

  5. 通過代理對象調(diào)用目標(biāo)方法;

public interface IHello {     void sayHello(); }
public class HelloImpl implements IHello {    @Override    public void sayHello() {        System.out.println("Hello world!");    }
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method;   public class MyInvocationHandler implements InvocationHandler {       /** 目標(biāo)對象 */     private Object target;       public MyInvocationHandler(Object target){         this.target = target;     }       @Override     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {         System.out.println("------插入前置通知代碼-------------");         // 執(zhí)行相應(yīng)的目標(biāo)方法         Object rs = method.invoke(target,args);         System.out.println("------插入后置處理代碼-------------");         return rs;     } }
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Proxy;  public class MyProxyTest {     public static void main(String[] args)             throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {         // =========================第一種==========================         // 1、生成$Proxy0的class文件         System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");         // 2、獲取動(dòng)態(tài)代理類         Class proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class);         // 3、獲得代理類的構(gòu)造函數(shù),并傳入?yún)?shù)類型InvocationHandler.class         Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);         // 4、通過構(gòu)造函數(shù)來創(chuàng)建動(dòng)態(tài)代理對象,將自定義的InvocationHandler實(shí)例傳入         IHello iHello1 = (IHello) constructor.newInstance(new MyInvocationHandler(new HelloImpl()));         // 5、通過代理對象調(diào)用目標(biāo)方法         iHello1.sayHello();           // ==========================第二種=============================         /**          * Proxy類中還有個(gè)將2~4步驟封裝好的簡便方法來創(chuàng)建動(dòng)態(tài)代理對象,          *其方法簽名為:newProxyInstance(ClassLoader loader,Class<?>[] instance, InvocationHandler h)          */         IHello  iHello2 = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), // 加載接口的類加載器                 new Class[]{IHello.class}, // 一組接口                 new MyInvocationHandler(new HelloImpl())); // 自定義的InvocationHandler         iHello2.sayHello();     } }

JDK靜態(tài)代理與JDK動(dòng)態(tài)代理之間有些許相似,比如說都要?jiǎng)?chuàng)建代理類,以及代理類都要實(shí)現(xiàn)接口等。

不同之處:  在靜態(tài)代理中我們需要對哪個(gè)接口和哪個(gè)被代理類創(chuàng)建代理類,所以我們在編譯前就需要代理類實(shí)現(xiàn)與被代理類相同的接口,并且直接在實(shí)現(xiàn)的方法中調(diào)用被代理類相應(yīng)的方法;但是動(dòng)態(tài)代理則不同,我們不知道要針對哪個(gè)接口、哪個(gè)被代理類創(chuàng)建代理類,因?yàn)樗窃谶\(yùn)行時(shí)被創(chuàng)建的。

一句話來總結(jié)一下JDK靜態(tài)代理和JDK動(dòng)態(tài)代理的區(qū)別:

JDK靜態(tài)代理是通過直接編碼創(chuàng)建的,而JDK動(dòng)態(tài)代理是利用反射機(jī)制在運(yùn)行時(shí)創(chuàng)建代理類的。

其實(shí)在動(dòng)態(tài)代理中,核心是InvocationHandler。每一個(gè)代理的實(shí)例都會(huì)有一個(gè)關(guān)聯(lián)的調(diào)用處理程序(InvocationHandler)。對待代理實(shí)例進(jìn)行調(diào)用時(shí),將對方法的調(diào)用進(jìn)行編碼并指派到它的調(diào)用處理器(InvocationHandler)的invoke方法

對代理對象實(shí)例方法的調(diào)用都是通過InvocationHandler中的invoke方法來完成的,而invoke方法會(huì)根據(jù)傳入的代理對象、方法名稱以及參數(shù)決定調(diào)用代理的哪個(gè)方法。

CGLIB

CGLIB包的底層是通過使用一個(gè)小而快的字節(jié)碼處理框架ASM,來轉(zhuǎn)換字節(jié)碼并生成新的類

CGLIB代理實(shí)現(xiàn)如下:

首先實(shí)現(xiàn)一個(gè)MethodInterceptor,方法調(diào)用會(huì)被轉(zhuǎn)發(fā)到該類的intercept()方法。

然后在需要使用的時(shí)候,通過CGLIB動(dòng)態(tài)代理獲取代理對象。

使用案例

 public class HelloService {       public HelloService() {         System.out.println("HelloService構(gòu)造");     }       /**      * 該方法不能被子類覆蓋,Cglib是無法代理final修飾的方法的      */     final public String sayOthers(String name) {         System.out.println("HelloService:sayOthers>>"+name);         return null;     }       public void sayHello() {         System.out.println("HelloService:sayHello");     } }
import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy;   import java.lang.reflect.Method;   /**  * 自定義MethodInterceptor  */ public class MyMethodInterceptor implements MethodInterceptor{       /**      * sub:cglib生成的代理對象      * method:被代理對象方法      * objects:方法入?yún)?nbsp;     * methodProxy: 代理方法      */     @Override     public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {         System.out.println("======插入前置通知======");         Object object = methodProxy.invokeSuper(sub, objects);         System.out.println("======插入后者通知======");         return object;     } }
import net.sf.cglib.core.DebuggingClassWriter; import net.sf.cglib.proxy.Enhancer;   public class Client {     public static void main(String[] args) {         // 代理類class文件存入本地磁盤方便我們反編譯查看源碼         System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");         // 通過CGLIB動(dòng)態(tài)代理獲取代理對象的過程         Enhancer enhancer = new Enhancer();         // 設(shè)置enhancer對象的父類         enhancer.setSuperclass(HelloService.class);         // 設(shè)置enhancer的回調(diào)對象         enhancer.setCallback(new MyMethodInterceptor());         // 創(chuàng)建代理對象         HelloService proxy= (HelloService)enhancer.create();         // 通過代理對象調(diào)用目標(biāo)方法         proxy.sayHello();     } }

JDK代理要求被代理的類必須實(shí)現(xiàn)接口,有很強(qiáng)的局限性。

而CGLIB動(dòng)態(tài)代理則沒有此類強(qiáng)制性要求。簡單的說,CGLIB會(huì)讓生成的代理類繼承被代理類,并在代理類中對代理方法進(jìn)行強(qiáng)化處理(前置處理、后置處理等)。

總結(jié)一下CGLIB在進(jìn)行代理的時(shí)候都進(jìn)行了哪些工作

  • 生成的代理類繼承被代理類。在這里我們需要注意一點(diǎn):如果委托類被final修飾,那么它不可被繼承,即不可被代理;同樣,如果委托類中存在final修飾的方法,那么該方法也不可被代理

  • 代理類會(huì)為委托方法生成兩個(gè)方法,一個(gè)是與委托方法簽名相同的方法,它在方法中會(huì)通過super調(diào)用委托方法;另一個(gè)是代理類獨(dú)有的方法

  • 當(dāng)執(zhí)行代理對象的方法時(shí),會(huì)首先判斷一下是否存在實(shí)現(xiàn)了MethodInterceptor接口的CGLIB$CALLBACK_0;,如果存在,則將調(diào)用MethodInterceptor中的intercept方法

在intercept方法中,我們除了會(huì)調(diào)用委托方法,還會(huì)進(jìn)行一些增強(qiáng)操作。在Spring  AOP中,典型的應(yīng)用場景就是在某些敏感方法執(zhí)行前后進(jìn)行操作日志記錄

在CGLIB中,方法的調(diào)用并不是通過反射來完成的,而是直接對方法進(jìn)行調(diào)用:通過FastClass機(jī)制對Class對象進(jìn)行特別的處理,比如將會(huì)用數(shù)組保存method的引用,每次調(diào)用方法的時(shí)候都是通過一個(gè)index下標(biāo)來保持對方法的引用

Fastclass機(jī)制

CGLIB采用了FastClass的機(jī)制來實(shí)現(xiàn)對被攔截方法的調(diào)用。

FastClass機(jī)制就是對一個(gè)類的方法建立索引,通過索引來直接調(diào)用相應(yīng)的方法

public class test10 {   //這里,tt可以看作目標(biāo)對象,fc可以看作是代理對象;首先根據(jù)代理對象的getIndex方法獲取目標(biāo)方法的索引,   //然后再調(diào)用代理對象的invoke方法就可以直接調(diào)用目標(biāo)類的方法,避免了反射     public static void main(String[] args){         Test tt = new Test();         Test2 fc = new Test2();         int index = fc.getIndex("f()V");         fc.invoke(index, tt, null);     } }  class Test{     public void f(){         System.out.println("f method");     }          public void g(){         System.out.println("g method");     } } class Test2{     public Object invoke(int index, Object o, Object[] ol){         Test t = (Test) o;         switch(index){         case 1:             t.f();             return null;         case 2:             t.g();             return null;         }         return null;     }     //這個(gè)方法對Test類中的方法建立索引     public int getIndex(String signature){         switch(signature.hashCode()){         case 3078479:             return 1;         case 3108270:             return 2;         }         return -1;     } }

上例中,Test2是Test的Fastclass,在Test2中有兩個(gè)方法getIndex和invoke。

在getIndex方法中對Test的每個(gè)方法建立索引,并根據(jù)入?yún)?方法名+方法的描述符)來返回相應(yīng)的索引。

Invoke根據(jù)指定的索引,以ol為入?yún)⒄{(diào)用對象O的方法。這樣就避免了反射調(diào)用,提高了效率

三種代理方式之間對比

代理方式實(shí)現(xiàn)優(yōu)點(diǎn)缺點(diǎn)特點(diǎn)
JDK靜態(tài)代理代理類與委托類實(shí)現(xiàn)同一接口,并且在代理類中需要硬編碼接口實(shí)現(xiàn)簡單,容易理解代理類需要硬編碼接口,在實(shí)際應(yīng)用中可能會(huì)導(dǎo)致重復(fù)編碼,浪費(fèi)存儲(chǔ)空間并且效率很低好像沒啥特點(diǎn)
JDK動(dòng)態(tài)代理代理類與委托類實(shí)現(xiàn)同一接口,主要是通過代理類實(shí)現(xiàn)InvocationHandler并重寫invoke方法來進(jìn)行動(dòng)態(tài)代理的,在invoke方法中將對方法進(jìn)行增強(qiáng)處理不需要硬編碼接口,代碼復(fù)用率高只能夠代理實(shí)現(xiàn)了接口的委托類底層使用反射機(jī)制進(jìn)行方法的調(diào)用
CGLIB動(dòng)態(tài)代理代理類將委托類作為自己的父類并為其中的非final委托方法創(chuàng)建兩個(gè)方法,一個(gè)是與委托方法簽名相同的方法,它在方法中會(huì)通過super調(diào)用委托方法;另一個(gè)是代理類獨(dú)有的方法。在代理方法中,它會(huì)判斷是否存在實(shí)現(xiàn)了MethodInterceptor接口的對象,若存在則將調(diào)用intercept方法對委托方法進(jìn)行代理可以在運(yùn)行時(shí)對類或者是接口進(jìn)行增強(qiáng)操作,且委托類無需實(shí)現(xiàn)接口不能對final類以及final方法進(jìn)行代理底層將方法全部存入一個(gè)數(shù)組中,通過數(shù)組索引直接進(jìn)行方法調(diào)用

問題

CGlib比JDK快?

  • 使用CGLiB實(shí)現(xiàn)動(dòng)態(tài)代理,CGLib底層采用ASM字節(jié)碼生成框架,使用字節(jié)碼技術(shù)生成代理類,  在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能對聲明為final的方法進(jìn)行代理,  因?yàn)镃GLib原理是動(dòng)態(tài)生成被代理類的子類。

  • 在jdk6、jdk7、jdk8逐步對JDK動(dòng)態(tài)代理優(yōu)化之后,在調(diào)用次數(shù)較少的情況下,JDK代理效率高于CGLIB代理效率。只有當(dāng)進(jìn)行大量調(diào)用的時(shí)候,jdk6和jdk7比CGLIB代理效率低一點(diǎn),但是到j(luò)dk8的時(shí)候,jdk代理效率高于CGLIB代理,總之,每一次jdk版本升級,jdk代理效率都得到提升,而CGLIB代理消息確有點(diǎn)跟不上步伐。

Spring如何選擇用JDK還是CGLIB?

  • 當(dāng)Bean實(shí)現(xiàn)接口時(shí),Spring就會(huì)用JDK的動(dòng)態(tài)代理。

  • 當(dāng)Bean沒有實(shí)現(xiàn)接口時(shí),Spring使用CGlib實(shí)現(xiàn)。

  • 可以強(qiáng)制使用CGlib

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

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

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

AI