Java 動態(tài)代理詳解

      網(wǎng)友投稿 951 2022-05-29

      微信原文:Java 動態(tài)代理詳解

      博客原文:Java 動態(tài)代理詳解

      動態(tài)代理在Java中有著廣泛的應(yīng)用,比如Spring AOP、Hibernate數(shù)據(jù)查詢、測試框架的后端mock、RPC遠(yuǎn)程調(diào)用、Java注解對象獲取、日志、用戶鑒權(quán)、全局性異常處理、性能監(jiān)控,甚至事務(wù)處理等。

      本文主要介紹Java中兩種常見的動態(tài)代理方式:JDK原生動態(tài)代理和CGLIB動態(tài)代理。

      由于Java動態(tài)代理與java反射機(jī)制關(guān)系緊密,請讀者確保已經(jīng)了解了Java反射機(jī)制,可參考上一篇文章《Java反射機(jī)制詳解》

      代理模式

      本文將介紹的Java動態(tài)代理與設(shè)計模式中的代理模式有關(guān),什么是代理模式呢?

      代理模式:給某一個對象提供一個代理,并由代理對象來控制對真實對象的訪問。代理模式是一種結(jié)構(gòu)型設(shè)計模式。

      代理模式角色分為 3 種:

      Subject(抽象主題角色):定義代理類和真實主題的公共對外方法,也是代理類代理真實主題的方法;

      RealSubject(真實主題角色):真正實現(xiàn)業(yè)務(wù)邏輯的類;

      Proxy(代理主題角色):用來代理和封裝真實主題;

      代理模式的結(jié)構(gòu)比較簡單,其核心是代理類,為了讓客戶端能夠一致性地對待真實對象和代理對象,在代理模式中引入了抽象層

      代理模式類圖

      代理模式按照職責(zé)(使用場景)來分類,至少可以分為以下幾類:1、遠(yuǎn)程代理。 2、虛擬代理。 3、Copy-on-Write 代理。 4、保護(hù)(Protect or Access)代理。 5、Cache代理。 6、防火墻(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理等等。

      如果根據(jù)字節(jié)碼的創(chuàng)建時機(jī)來分類,可以分為靜態(tài)代理和動態(tài)代理:

      所謂靜態(tài)也就是在程序運(yùn)行前就已經(jīng)存在代理類的字節(jié)碼文件,代理類和真實主題角色的關(guān)系在運(yùn)行前就確定了。

      微信原文:Java 動態(tài)代理詳解

      博客原文:Java 動態(tài)代理詳解

      動態(tài)代理在Java中有著廣泛的應(yīng)用,比如Spring AOP、Hibernate數(shù)據(jù)查詢、測試框架的后端mock、RPC遠(yuǎn)程調(diào)用、Java注解對象獲取、日志、用戶鑒權(quán)、全局性異常處理、性能監(jiān)控,甚至事務(wù)處理等。

      本文主要介紹Java中兩種常見的動態(tài)代理方式:JDK原生動態(tài)代理和CGLIB動態(tài)代理。

      由于Java動態(tài)代理與java反射機(jī)制關(guān)系緊密,請讀者確保已經(jīng)了解了Java反射機(jī)制,可參考上一篇文章《Java反射機(jī)制詳解》

      代理模式

      本文將介紹的Java動態(tài)代理與設(shè)計模式中的代理模式有關(guān),什么是代理模式呢?

      代理模式:給某一個對象提供一個代理,并由代理對象來控制對真實對象的訪問。代理模式是一種結(jié)構(gòu)型設(shè)計模式。

      代理模式角色分為 3 種:

      Subject(抽象主題角色):定義代理類和真實主題的公共對外方法,也是代理類代理真實主題的方法;

      RealSubject(真實主題角色):真正實現(xiàn)業(yè)務(wù)邏輯的類;

      Proxy(代理主題角色):用來代理和封裝真實主題;

      代理模式的結(jié)構(gòu)比較簡單,其核心是代理類,為了讓客戶端能夠一致性地對待真實對象和代理對象,在代理模式中引入了抽象層

      代理模式按照職責(zé)(使用場景)來分類,至少可以分為以下幾類:1、遠(yuǎn)程代理。 2、虛擬代理。 3、Copy-on-Write 代理。 4、保護(hù)(Protect or Access)代理。 5、Cache代理。 6、防火墻(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理等等。

      如果根據(jù)字節(jié)碼的創(chuàng)建時機(jī)來分類,可以分為靜態(tài)代理和動態(tài)代理:

      所謂靜態(tài)也就是在程序運(yùn)行前就已經(jīng)存在代理類的字節(jié)碼文件,代理類和真實主題角色的關(guān)系在運(yùn)行前就確定了。

      而動態(tài)代理的源碼是在程序運(yùn)行期間由JVM根據(jù)反射等機(jī)制動態(tài)的生成,所以在運(yùn)行前并不存在代理類的字節(jié)碼文件

      靜態(tài)代理

      我們先通過實例來 learn 靜態(tài)代理,然后理解靜態(tài)代理的缺點,再來學(xué)習(xí)本文的主角:動態(tài)代理

      編寫一個接口 UserService ,以及該接口的一個實現(xiàn)類 UserServiceImpl

      public?interface?UserService?{

      public?void?select();

      public?void?update();

      }

      public?class?UserServiceImpl?implements?UserService?{

      public?void?select()?{

      System.out.println("查詢?selectById");

      }

      public?void?update()?{

      System.out.println("更新?update");

      }

      }

      我們將通過靜態(tài)代理對 UserServiceImpl 進(jìn)行功能增強(qiáng),在調(diào)用 select 和 update 之前記錄一些日志。寫一個代理類 UserServiceProxy,代理類需要實現(xiàn) UserService

      public?class?UserServiceProxy?implements?UserService?{

      private?UserService?target;?//?被代理的對象

      public?UserServiceProxy(UserService?target)?{

      this.target?=?target;

      }

      public?void?select()?{

      before();

      target.select();????//?這里才實際調(diào)用真實主題角色的方法

      after();

      }

      public?void?update()?{

      before();

      target.update();????//?這里才實際調(diào)用真實主題角色的方法

      after();

      }

      private?void?before()?{?????//?在執(zhí)行方法之前執(zhí)行

      System.out.println(String.format("log?start?time?[%s]?",?new?Date()));

      }

      private?void?after()?{??????//?在執(zhí)行方法之后執(zhí)行

      System.out.println(String.format("log?end?time?[%s]?",?new?Date()));

      }

      }

      客戶端測試

      public?class?Client1?{

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

      UserService?userServiceImpl?=?new?UserServiceImpl();

      UserService?proxy?=?new?UserServiceProxy(userServiceImpl);

      proxy.select();

      proxy.update();

      }

      }

      輸出

      log?start?time?[Thu?Dec?20?14:13:25?CST?2018]

      查詢?selectById

      log?end?time?[Thu?Dec?20?14:13:25?CST?2018]

      log?start?time?[Thu?Dec?20?14:13:25?CST?2018]

      更新?update

      log?end?time?[Thu?Dec?20?14:13:25?CST?2018]

      通過靜態(tài)代理,我們達(dá)到了功能增強(qiáng)的目的,而且沒有侵入原代碼,這是靜態(tài)代理的一個優(yōu)點。

      雖然靜態(tài)代理實現(xiàn)簡單,且不侵入原代碼,但是,當(dāng)場景稍微復(fù)雜一些的時候,靜態(tài)代理的缺點也會暴露出來。

      1、 當(dāng)需要代理多個類的時候,由于代理對象要實現(xiàn)與目標(biāo)對象一致的接口,有兩種方式:

      只維護(hù)一個代理類,由這個代理類實現(xiàn)多個接口,但是這樣就導(dǎo)致代理類過于龐大

      新建多個代理類,每個目標(biāo)對象對應(yīng)一個代理類,但是這樣會產(chǎn)生過多的代理類

      2、 當(dāng)接口需要增加、刪除、修改方法的時候,目標(biāo)對象與代理類都要同時修改,不易維護(hù)。

      當(dāng)然是讓代理類動態(tài)的生成啦,也就是動態(tài)代理。

      為什么類可以動態(tài)的生成?

      這就涉及到Java虛擬機(jī)的類加載機(jī)制了,推薦翻看《深入理解Java虛擬機(jī)》7.3節(jié) 類加載的過程。

      Java虛擬機(jī)類加載過程主要分為五個階段:加載、驗證、準(zhǔn)備、解析、初始化。其中加載階段需要完成以下3件事情:

      通過一個類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流

      將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時數(shù)據(jù)結(jié)構(gòu)

      在內(nèi)存中生成一個代表這個類的 java.lang.Class 對象,作為方法區(qū)這個類的各種數(shù)據(jù)訪問入口

      由于虛擬機(jī)規(guī)范對這3點要求并不具體,所以實際的實現(xiàn)是非常靈活的,關(guān)于第1點,獲取類的二進(jìn)制字節(jié)流(class字節(jié)碼)就有很多途徑:

      從ZIP包獲取,這是JAR、EAR、WAR等格式的基礎(chǔ)

      從網(wǎng)絡(luò)中獲取,典型的應(yīng)用是 Applet

      運(yùn)行時計算生成,這種場景使用最多的是動態(tài)代理技術(shù),在 java.lang.reflect.Proxy 類中,就是用了 ProxyGenerator.generateProxyClass 來為特定接口生成形式為 *$Proxy 的代理類的二進(jìn)制字節(jié)流

      由其它文件生成,典型應(yīng)用是JSP,即由JSP文件生成對應(yīng)的Class類

      從數(shù)據(jù)庫中獲取等等

      所以,動態(tài)代理就是想辦法,根據(jù)接口或目標(biāo)對象,計算出代理類的字節(jié)碼,然后再加載到JVM中使用。但是如何計算?如何生成?情況也許比想象的復(fù)雜得多,我們需要借助現(xiàn)有的方案。

      這里有一些介紹:https://java-source.net/open-source/bytecode-libraries

      Apache BCEL (Byte Code Engineering Library):是Java classworking廣泛使用的一種框架,它可以深入到JVM匯編語言進(jìn)行類操作的細(xì)節(jié)。

      ObjectWeb ASM:是一個Java字節(jié)碼操作框架。它可以用于直接以二進(jìn)制形式動態(tài)生成stub根類或其他代理類,或者在加載時動態(tài)修改類。

      CGLIB(Code Generation Library):是一個功能強(qiáng)大,高性能和高質(zhì)量的代碼生成庫,用于擴(kuò)展JAVA類并在運(yùn)行時實現(xiàn)接口。

      Javassist:是Java的加載時反射系統(tǒng),它是一個用于在Java中編輯字節(jié)碼的類庫; 它使Java程序能夠在運(yùn)行時定義新類,并在JVM加載之前修改類文件。

      為了讓生成的代理類與目標(biāo)對象(真實主題角色)保持一致性,從現(xiàn)在開始將介紹以下兩種最常見的方式:

      通過實現(xiàn)接口的方式 -> JDK動態(tài)代理

      通過繼承類的方式 -> CGLIB動態(tài)代理

      注:使用ASM對使用者要求比較高,使用Javassist會比較麻煩

      JDK動態(tài)代理

      JDK動態(tài)代理主要涉及兩個類:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler,我們?nèi)匀煌ㄟ^案例來學(xué)習(xí)

      編寫一個調(diào)用邏輯處理器 LogHandler 類,提供日志增強(qiáng)功能,并實現(xiàn) InvocationHandler 接口;在 LogHandler 中維護(hù)一個目標(biāo)對象,這個對象是被代理的對象(真實主題角色);在 invoke 方法中編寫方法調(diào)用的邏輯處理

      import?java.lang.reflect.InvocationHandler;

      import?java.lang.reflect.Method;

      import?java.util.Date;

      public?class?LogHandler?implements?InvocationHandler?{

      Object?target;??//?被代理的對象,實際的方法執(zhí)行者

      public?LogHandler(Object?target)?{

      this.target?=?target;

      }

      @Override

      public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{

      before();

      Object?result?=?method.invoke(target,?args);??//?調(diào)用?target?的?method?方法

      after();

      return?result;??//?返回方法的執(zhí)行結(jié)果

      }

      //?調(diào)用invoke方法之前執(zhí)行

      private?void?before()?{

      System.out.println(String.format("log?start?time?[%s]?",?new?Date()));

      }

      //?調(diào)用invoke方法之后執(zhí)行

      private?void?after()?{

      System.out.println(String.format("log?end?time?[%s]?",?new?Date()));

      }

      }

      編寫客戶端,獲取動態(tài)生成的代理類的對象須借助 Proxy 類的 newProxyInstance 方法,具體步驟可見代碼和注釋

      import?proxy.UserService;

      import?proxy.UserServiceImpl;

      import?java.lang.reflect.InvocationHandler;

      import?java.lang.reflect.Proxy;

      public?class?Client2?{

      public?static?void?main(String[]?args)?throws?IllegalAccessException,?InstantiationException?{

      //?設(shè)置變量可以保存動態(tài)代理類,默認(rèn)名稱以?$Proxy0?格式命名

      //?System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles",?"true");

      //?1.?創(chuàng)建被代理的對象,UserService接口的實現(xiàn)類

      UserServiceImpl?userServiceImpl?=?new?UserServiceImpl();

      //?2.?獲取對應(yīng)的?ClassLoader

      ClassLoader?classLoader?=?userServiceImpl.getClass().getClassLoader();

      //?3.?獲取所有接口的Class,這里的UserServiceImpl只實現(xiàn)了一個接口UserService,

      Class[]?interfaces?=?userServiceImpl.getClass().getInterfaces();

      //?4.?創(chuàng)建一個將傳給代理類的調(diào)用請求處理器,處理所有的代理對象上的方法調(diào)用

      //?????這里創(chuàng)建的是一個自定義的日志處理器,須傳入實際的執(zhí)行對象?userServiceImpl

      InvocationHandler?logHandler?=?new?LogHandler(userServiceImpl);

      /*

      5.根據(jù)上面提供的信息,創(chuàng)建代理對象?在這個過程中,

      a.JDK會通過根據(jù)傳入的參數(shù)信息動態(tài)地在內(nèi)存中創(chuàng)建和.class?文件等同的字節(jié)碼

      b.然后根據(jù)相應(yīng)的字節(jié)碼轉(zhuǎn)換成對應(yīng)的class,

      c.然后調(diào)用newInstance()創(chuàng)建代理實例

      */

      UserService?proxy?=?(UserService)?Proxy.newProxyInstance(classLoader,?interfaces,?logHandler);

      //?調(diào)用代理的方法

      proxy.select();

      proxy.update();

      //?保存JDK動態(tài)代理生成的代理類,類名保存為?UserServiceProxy

      //?ProxyUtils.generateClassFile(userServiceImpl.getClass(),?"UserServiceProxy");

      }

      }

      運(yùn)行結(jié)果

      log?start?time?[Thu?Dec?20?16:55:19?CST?2018]

      查詢?selectById

      log?end?time?[Thu?Dec?20?16:55:19?CST?2018]

      log?start?time?[Thu?Dec?20?16:55:19?CST?2018]

      更新?update

      log?end?time?[Thu?Dec?20?16:55:19?CST?2018]

      InvocationHandler 和 Proxy 的主要方法介紹如下:

      java.lang.reflect.InvocationHandler

      Object invoke(Object proxy, Method method, Object[] args) 定義了代理對象調(diào)用方法時希望執(zhí)行的動作,用于集中處理在動態(tài)代理類對象上的方法調(diào)用

      java.lang.reflect.Proxy

      static InvocationHandler getInvocationHandler(Object proxy) ?用于獲取指定代理對象所關(guān)聯(lián)的調(diào)用處理器

      static Class getProxyClass(ClassLoader loader, Class... interfaces) 返回指定接口的代理類

      static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 構(gòu)造實現(xiàn)指定接口的代理類的一個新實例,所有方法會調(diào)用給定處理器對象的 invoke 方法

      static boolean isProxyClass(Class cl) 返回 cl 是否為一個代理類

      生成的代理類到底長什么樣子呢?借助下面的工具類,把代理類保存下來再探個究竟

      (通過設(shè)置環(huán)境變量sun.misc.ProxyGenerator.saveGeneratedFiles=true也可以保存代理類)

      import?sun.misc.ProxyGenerator;

      import?java.io.FileOutputStream;

      import?java.io.IOException;

      public?class?ProxyUtils?{

      /**

      *?將根據(jù)類信息動態(tài)生成的二進(jìn)制字節(jié)碼保存到硬盤中,默認(rèn)的是clazz目錄下

      *?params:?clazz?需要生成動態(tài)代理類的類

      *?proxyName:?為動態(tài)生成的代理類的名稱

      */

      public?static?void?generateClassFile(Class?clazz,?String?proxyName)?{

      //?根據(jù)類信息和提供的代理類名稱,生成字節(jié)碼

      byte[]?classFile?=?ProxyGenerator.generateProxyClass(proxyName,?clazz.getInterfaces());

      String?paths?=?clazz.getResource(".").getPath();

      System.out.println(paths);

      FileOutputStream?out?=?null;

      try?{

      //保留到硬盤中

      out?=?new?FileOutputStream(paths?+?proxyName?+?".class");

      out.write(classFile);

      out.flush();

      }?catch?(Exception?e)?{

      e.printStackTrace();

      }?finally?{

      try?{

      out.close();

      }?catch?(IOException?e)?{

      e.printStackTrace();

      }

      }

      }

      }

      然后在 Client2 測試類的main的最后面加入一行代碼

      //?保存JDK動態(tài)代理生成的代理類,類名保存為?UserServiceProxy

      Java 動態(tài)代理詳解

      ProxyUtils.generateClassFile(userServiceImpl.getClass(),?"UserServiceProxy");

      IDEA 再次運(yùn)行之后就可以在 target 的類路徑下找到 UserServiceProxy.class,雙擊后IDEA的反編譯插件會將該二進(jìn)制class文件

      UserServiceProxy 的代碼如下所示:

      import?java.lang.reflect.InvocationHandler;

      import?java.lang.reflect.Method;

      import?java.lang.reflect.Proxy;

      import?java.lang.reflect.UndeclaredThrowableException;

      import?proxy.UserService;

      public?final?class?UserServiceProxy?extends?Proxy?implements?UserService?{

      private?static?Method?m1;

      private?static?Method?m2;

      private?static?Method?m4;

      private?static?Method?m0;

      private?static?Method?m3;

      public?UserServiceProxy(InvocationHandler?var1)?throws??{

      super(var1);

      }

      public?final?boolean?equals(Object?var1)?throws??{

      //?省略...

      }

      public?final?String?toString()?throws??{

      //?省略...

      }

      public?final?void?select()?throws??{

      try?{

      super.h.invoke(this,?m4,?(Object[])null);

      }?catch?(RuntimeException?|?Error?var2)?{

      throw?var2;

      }?catch?(Throwable?var3)?{

      throw?new?UndeclaredThrowableException(var3);

      }

      }

      public?final?int?hashCode()?throws??{

      //?省略...

      }

      public?final?void?update()?throws??{

      try?{

      super.h.invoke(this,?m3,?(Object[])null);

      }?catch?(RuntimeException?|?Error?var2)?{

      throw?var2;

      }?catch?(Throwable?var3)?{

      throw?new?UndeclaredThrowableException(var3);

      }

      }

      static?{

      try?{

      m1?=?Class.forName("java.lang.Object").getMethod("equals",?Class.forName("java.lang.Object"));

      m2?=?Class.forName("java.lang.Object").getMethod("toString");

      m4?=?Class.forName("proxy.UserService").getMethod("select");

      m0?=?Class.forName("java.lang.Object").getMethod("hashCode");

      m3?=?Class.forName("proxy.UserService").getMethod("update");

      }?catch?(NoSuchMethodException?var2)?{

      throw?new?NoSuchMethodError(var2.getMessage());

      }?catch?(ClassNotFoundException?var3)?{

      throw?new?NoClassDefFoundError(var3.getMessage());

      }

      }

      }

      從 UserServiceProxy 的代碼中我們可以發(fā)現(xiàn):

      UserServiceProxy 繼承了 Proxy 類,并且實現(xiàn)了被代理的所有接口,以及equals、hashCode、toString等方法

      由于 UserServiceProxy 繼承了 Proxy 類,所以每個代理類都會關(guān)聯(lián)一個 InvocationHandler 方法調(diào)用處理器

      類和所有方法都被 public final 修飾,所以代理類只可被使用,不可以再被繼承

      每個方法都有一個 Method 對象來描述,Method 對象在static靜態(tài)代碼塊中創(chuàng)建,以 m + 數(shù)字 的格式命名

      調(diào)用方法的時候通過 super.h.invoke(this, m1, (Object[])null); 調(diào)用,其中的 super.h.invoke 實際上是在創(chuàng)建代理的時候傳遞給 Proxy.newProxyInstance 的 LogHandler 對象,它繼承 InvocationHandler 類,負(fù)責(zé)實際的調(diào)用處理邏輯

      而 LogHandler 的 invoke 方法接收到 method、args 等參數(shù)后,進(jìn)行一些處理,然后通過反射讓被代理的對象 target 執(zhí)行方法

      @Override

      public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{

      before();

      Object?result?=?method.invoke(target,?args);???????//?調(diào)用?target?的?method?方法

      after();

      return?result;??//?返回方法的執(zhí)行結(jié)果

      }

      JDK動態(tài)代理執(zhí)行方法調(diào)用的過程簡圖如下:

      代理類的調(diào)用過程相信大家都明了了,而關(guān)于Proxy的源碼解析,還請大家另外查閱其他文章或者直接看源碼

      CGLIB動態(tài)代理

      maven引入CGLIB包,然后編寫一個UserDao類,它沒有接口,只有兩個方法,select() 和 update()

      public?class?UserDao?{

      public?void?select()?{

      System.out.println("UserDao?查詢?selectById");

      }

      public?void?update()?{

      System.out.println("UserDao?更新?update");

      }

      }

      編寫一個 LogInterceptor ,繼承了 MethodInterceptor,用于方法的攔截回調(diào)

      import?java.lang.reflect.Method;

      import?java.util.Date;

      public?class?LogInterceptor?implements?MethodInterceptor?{

      /**

      *?@param?object?表示要進(jìn)行增強(qiáng)的對象

      *?@param?method?表示攔截的方法

      *?@param?objects?數(shù)組表示參數(shù)列表,基本數(shù)據(jù)類型需要傳入其包裝類型,如int-->Integer、long-Long、double-->Double

      *?@param?methodProxy?表示對方法的代理,invokeSuper方法表示對被代理對象方法的調(diào)用

      *?@return?執(zhí)行結(jié)果

      *?@throws?Throwable

      */

      @Override

      public?Object?intercept(Object?object,?Method?method,?Object[]?objects,?MethodProxy?methodProxy)?throws?Throwable?{

      before();

      Object?result?=?methodProxy.invokeSuper(object,?objects);???//?注意這里是調(diào)用?invokeSuper?而不是?invoke,否則死循環(huán),methodProxy.invokesuper執(zhí)行的是原始類的方法,method.invoke執(zhí)行的是子類的方法

      after();

      return?result;

      }

      private?void?before()?{

      System.out.println(String.format("log?start?time?[%s]?",?new?Date()));

      }

      private?void?after()?{

      System.out.println(String.format("log?end?time?[%s]?",?new?Date()));

      }

      }

      測試

      import?net.sf.cglib.proxy.Enhancer;

      public?class?CglibTest?{

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

      DaoProxy?daoProxy?=?new?DaoProxy();

      Enhancer?enhancer?=?new?Enhancer();

      enhancer.setSuperclass(Dao.class);??//?設(shè)置超類,cglib是通過繼承來實現(xiàn)的

      enhancer.setCallback(daoProxy);

      Dao?dao?=?(Dao)enhancer.create();???//?創(chuàng)建代理類

      dao.update();

      dao.select();

      }

      }

      運(yùn)行結(jié)果

      log?start?time?[Fri?Dec?21?00:06:40?CST?2018]

      UserDao?查詢?selectById

      log?end?time?[Fri?Dec?21?00:06:40?CST?2018]

      log?start?time?[Fri?Dec?21?00:06:40?CST?2018]

      UserDao?更新?update

      log?end?time?[Fri?Dec?21?00:06:40?CST?2018]

      還可以進(jìn)一步多個 MethodInterceptor 進(jìn)行過濾篩選

      public?class?LogInterceptor2?implements?MethodInterceptor?{

      @Override

      public?Object?intercept(Object?object,?Method?method,?Object[]?objects,?MethodProxy?methodProxy)?throws?Throwable?{

      before();

      Object?result?=?methodProxy.invokeSuper(object,?objects);

      after();

      return?result;

      }

      private?void?before()?{

      System.out.println(String.format("log2?start?time?[%s]?",?new?Date()));

      }

      private?void?after()?{

      System.out.println(String.format("log2?end?time?[%s]?",?new?Date()));

      }

      }

      //?回調(diào)過濾器:?在CGLib回調(diào)時可以設(shè)置對不同方法執(zhí)行不同的回調(diào)邏輯,或者根本不執(zhí)行回調(diào)。

      public?class?DaoFilter?implements?CallbackFilter?{

      @Override

      public?int?accept(Method?method)?{

      if?("select".equals(method.getName()))?{

      return?0;???//?Callback?列表第1個-

      }

      return?1;???//?Callback?列表第2個-,return?2?則為第3個,以此類推

      }

      }

      再次測試

      public?class?CglibTest2?{

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

      LogInterceptor?logInterceptor?=?new?LogInterceptor();

      LogInterceptor2?logInterceptor2?=?new?LogInterceptor2();

      Enhancer?enhancer?=?new?Enhancer();

      enhancer.setSuperclass(UserDao.class);???//?設(shè)置超類,cglib是通過繼承來實現(xiàn)的

      enhancer.setCallbacks(new?Callback[]{logInterceptor,?logInterceptor2,?NoOp.INSTANCE});???//?設(shè)置多個-,NoOp.INSTANCE是一個空-,不做任何處理

      enhancer.setCallbackFilter(new?DaoFilter());

      UserDao?proxy?=?(UserDao)?enhancer.create();???//?創(chuàng)建代理類

      proxy.select();

      proxy.update();

      }

      }

      運(yùn)行結(jié)果

      log?start?time?[Fri?Dec?21?00:22:39?CST?2018]

      UserDao?查詢?selectById

      log?end?time?[Fri?Dec?21?00:22:39?CST?2018]

      log2?start?time?[Fri?Dec?21?00:22:39?CST?2018]

      UserDao?更新?update

      log2?end?time?[Fri?Dec?21?00:22:39?CST?2018]

      CGLIB 創(chuàng)建動態(tài)代理類的模式是:

      查找目標(biāo)類上的所有非final 的public類型的方法定義;

      將這些方法的定義轉(zhuǎn)換成字節(jié)碼;

      將組成的字節(jié)碼轉(zhuǎn)換成相應(yīng)的代理的class對象;

      實現(xiàn) MethodInterceptor接口,用來處理對代理類上所有方法的請求

      JDK動態(tài)代理與CGLIB動態(tài)代理對比

      JDK動態(tài)代理:基于Java反射機(jī)制實現(xiàn),必須要實現(xiàn)了接口的業(yè)務(wù)類才能用這種辦法生成代理對象。

      cglib動態(tài)代理:基于ASM機(jī)制實現(xiàn),通過生成業(yè)務(wù)類的子類作為代理類。

      JDK Proxy 的優(yōu)勢:

      最小化依賴關(guān)系,減少依賴意味著簡化開發(fā)和維護(hù),JDK 本身的支持,可能比 cglib 更加可靠。

      平滑進(jìn)行 JDK 版本升級,而字節(jié)碼類庫通常需要進(jìn)行更新以保證在新版 Java 上能夠使用。

      代碼實現(xiàn)簡單。

      基于類似 cglib 框架的優(yōu)勢:

      無需實現(xiàn)接口,達(dá)到代理類無侵入

      只操作我們關(guān)心的類,而不必為其他相關(guān)類增加工作量。

      高性能

      面試題

      來源于網(wǎng)上,用于幫助理解和掌握,歡迎補(bǔ)充

      代理可以分為 "靜態(tài)代理" 和 "動態(tài)代理",動態(tài)代理又分為 "JDK動態(tài)代理" 和 "CGLIB動態(tài)代理" 實現(xiàn)。

      靜態(tài)代理:代理對象和實際對象都繼承了同一個接口,在代理對象中指向的是實際對象的實例,這樣對外暴露的是代理對象而真正調(diào)用的是 Real Object

      優(yōu)點:可以很好的保護(hù)實際對象的業(yè)務(wù)邏輯對外暴露,從而提高安全性。

      缺點:不同的接口要有不同的代理類實現(xiàn),會很冗余

      JDK 動態(tài)代理:

      為了解決靜態(tài)代理中,生成大量的代理類造成的冗余;

      JDK 動態(tài)代理只需要實現(xiàn) InvocationHandler 接口,重寫 invoke 方法便可以完成代理的實現(xiàn),

      jdk的代理是利用反射生成代理類 Proxyxx.class 代理類字節(jié)碼,并生成對象

      jdk動態(tài)代理之所以只能代理接口是因為代理類本身已經(jīng)extends了Proxy,而java是不允許多重繼承的,但是允許實現(xiàn)多個接口

      優(yōu)點:解決了靜態(tài)代理中冗余的代理實現(xiàn)類問題。

      缺點:JDK 動態(tài)代理是基于接口設(shè)計實現(xiàn)的,如果沒有接口,會拋異常。

      CGLIB 代理:

      由于 JDK 動態(tài)代理限制了只能基于接口設(shè)計,而對于沒有接口的情況,JDK方式解決不了;

      CGLib 采用了非常底層的字節(jié)碼技術(shù),其原理是通過字節(jié)碼技術(shù)為一個類創(chuàng)建子類,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,順勢織入橫切邏輯,來完成動態(tài)代理的實現(xiàn)。

      實現(xiàn)方式實現(xiàn) MethodInterceptor 接口,重寫 intercept 方法,通過 Enhancer 類的回調(diào)方法來實現(xiàn)。

      但是CGLib在創(chuàng)建代理對象時所花費的時間卻比JDK多得多,所以對于單例的對象,因為無需頻繁創(chuàng)建對象,用CGLib合適,反之,使用JDK方式要更為合適一些。

      同時,由于CGLib由于是采用動態(tài)創(chuàng)建子類的方法,對于final方法,無法進(jìn)行代理。

      優(yōu)點:沒有接口也能實現(xiàn)動態(tài)代理,而且采用字節(jié)碼增強(qiáng)技術(shù),性能也不錯。

      缺點:技術(shù)實現(xiàn)相對難理解些。

      import?net.sf.cglib.proxy.Enhancer;

      import?net.sf.cglib.proxy.MethodInterceptor;

      import?net.sf.cglib.proxy.MethodProxy;

      import?proxy.UserService;

      import?java.lang.reflect.Method;

      /**

      *?創(chuàng)建代理類的工廠?該類要實現(xiàn)?MethodInterceptor?接口。

      *?該類中完成三樣工作:

      *?(1)聲明目標(biāo)類的成員變量,并創(chuàng)建以目標(biāo)類對象為參數(shù)的構(gòu)造器。用于接收目標(biāo)對象

      *?(2)定義代理的生成方法,用于創(chuàng)建代理對象。方法名是任意的。代理對象即目標(biāo)類的子類

      *?(3)定義回調(diào)接口方法。對目標(biāo)類的增強(qiáng)這在這里完成

      */

      public?class?CGLibFactory?implements?MethodInterceptor?{

      //?聲明目標(biāo)類的成員變量

      private?UserService?target;

      public?CGLibFactory(UserService?target)?{

      this.target?=?target;

      }

      //?定義代理的生成方法,用于創(chuàng)建代理對象

      public?UserService?myCGLibCreator()?{

      Enhancer?enhancer?=?new?Enhancer();

      //?為代理對象設(shè)置父類,即指定目標(biāo)類

      enhancer.setSuperclass(UserService.class);

      /**

      *?設(shè)置回調(diào)接口對象?注意,只所以在setCallback()方法中可以寫上this,

      *?是因為MethodIntecepter接口繼承自Callback,是其子接口

      */

      enhancer.setCallback(this);

      return?(UserService)?enhancer.create();//?create用以生成CGLib代理對象

      }

      @Override

      public?Object?intercept(Object?obj,?Method?method,?Object[]?args,?MethodProxy?proxy)?throws?Throwable?{

      System.out.println("start?invoke?"?+?method.getName());

      Object?result?=?method.invoke(target,?args);

      System.out.println("end?invoke?"?+?method.getName());

      return?result;

      }

      }

      參考:

      《Java核心技術(shù)》卷1

      《深入理解Java虛擬機(jī)》7.3

      java docs: https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html

      Java三種代理模式:靜態(tài)代理、動態(tài)代理和cglib代理

      描述動態(tài)代理的幾種實現(xiàn)方式 分別說出相應(yīng)的優(yōu)缺點

      JDK動態(tài)代理詳解

      Java動態(tài)代理機(jī)制詳解(JDK 和CGLIB,Javassist,ASM)

      靜態(tài)代理和動態(tài)代理的理解

      后記

      歡迎評論、轉(zhuǎn)發(fā)、分享,您的支持是我最大的動力

      更多內(nèi)容可訪問我的個人博客:http://laijianfeng.org

      關(guān)注【小旋鋒】微信公眾號,及時接收博文推送

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:基于OMAP-L138的便攜式設(shè)備狀態(tài)監(jiān)測與診斷儀設(shè)計
      下一篇:為什么自制腳本語言是編程語言的最高境界?
      相關(guān)文章
      亚洲国产精品无码久久久不卡| 亚洲精品无码专区2| 亚洲AV日韩精品一区二区三区 | 亚洲精品tv久久久久| 亚洲a无码综合a国产av中文| 国产AV旡码专区亚洲AV苍井空| 亚洲人妖女同在线播放| 亚洲成a人片77777群色| 91亚洲国产在人线播放午夜| 久久久久亚洲av无码专区喷水| 久久精品国产亚洲AV麻豆~| 久久亚洲成a人片| 亚洲国产精品久久久久婷婷软件| 亚洲av无码成h人动漫无遮挡 | 中文字幕亚洲乱码熟女一区二区 | 亚洲欧洲精品成人久久曰影片| 亚洲 综合 国产 欧洲 丝袜| 亚洲av区一区二区三| 亚洲精品高清在线| 亚洲一区视频在线播放| 国产亚洲情侣一区二区无码AV| 亚洲熟妇丰满多毛XXXX| 久久精品国产亚洲沈樵| 久久久久亚洲av无码尤物| 亚洲国产精品久久66| 亚洲综合在线成人一区| 亚洲国产夜色在线观看| ASS亚洲熟妇毛茸茸PICS| 亚洲色一区二区三区四区| 亚洲AV无码国产一区二区三区| 综合偷自拍亚洲乱中文字幕| 日韩亚洲人成网站| 亚洲天堂在线视频| 国产V亚洲V天堂无码久久久| 无码乱人伦一区二区亚洲| 亚洲成a人片77777群色| 亚洲日韩国产二区无码| 亚洲第一区在线观看| 亚洲精品无码成人AAA片| 亚洲高清在线视频| 99热亚洲色精品国产88|