Java動態代理機制詳解(JDK 和CGLIB,Javassist,ASM)html
完全理解JAVA動態代理java
Java編譯器編譯好Java文件以後,產生.class 文件在磁盤中。這種class文件是二進制文件,內容是隻有JVM虛擬機可以識別的機器碼。JVM虛擬機讀取字節碼文件,取出二進制數據,加載到內存中,解析.class 文件內的信息,生成對應的 Class對象:編程
因爲JVM經過字節碼的二進制信息加載類的,那麼,若是咱們在運行期系統中,遵循Java編譯系統組織.class文件的格式和結構,生成相應的二進制數據,而後再把這個二進制數據加載轉換成對應的類,這樣,就完成了在代碼中,動態建立一個類的能力了。工具
代理模式上,基本上有Subject角色,RealSubject角色,Proxy角色。其中:Subject角色負責定義RealSubject和Proxy角色應該實現的接口;RealSubject角色用來真正完成業務服務功能;Proxy角色負責將自身的Request請求,調用realsubject 對應的request功能來實現業務功能,本身不真正作業務。post
![]()
上面的這幅代理結構圖是典型的靜態的代理模式:性能
當在代碼階段規定這種代理關係,Proxy類經過編譯器編譯成class文件,當系統運行時,此class已經存在了。這種靜態的代理模式當然在訪問沒法訪問的資源,加強現有的接口業務功能方面有很大的優勢,可是大量使用這種靜態代理,會使咱們系統內的類的規模增大,而且不易維護;而且因爲Proxy和RealSubject的功能 本質上是相同的,Proxy只是起到了中介的做用,這種代理在系統中的存在,致使系統結構比較臃腫和鬆散。ui
爲了解決這個問題,就有了動態地建立Proxy的想法:在運行狀態中,須要代理的地方,根據Subject 和RealSubject,動態地建立一個Proxy,用完以後,就會銷燬,這樣就能夠避免了Proxy 角色的class在系統中冗雜的問題了。lua
下面以一個代理模式實例闡述這一問題:url
將車站的售票服務抽象出一個接口TicketService,包含問詢,賣票,退票功能,車站類Station實現了TicketService接口,車票代售點StationProxy則實現了代理角色的功能,類圖以下所示。spa
仔細思考代理模式中的代理Proxy角色。Proxy角色在執行代理業務的時候,無非是在調用真正業務以前或者以後作一些「額外」業務。
有上圖能夠看出,代理類處理的邏輯很簡單:在調用某個方法前及方法後作一些額外的業務。換一種思路就是:在觸發(invoke)真實角色的方法以前或者以後作一些額外的業務。那麼,爲了構造出具備通用性和簡單性的代理類,能夠將全部的觸發真實角色動做交給一個觸發的管理器,讓這個管理器統一地管理觸發。這種管理器就是Invocation Handler。
動態代理模式的結構跟上面的靜態代理模式稍微有所不一樣,多引入了一個InvocationHandler角色。
先解釋一下InvocationHandler的做用:
在靜態代理中,代理Proxy中的方法,都指定了調用了特定的realSubject中的對應的方法:
在上面的靜態代理模式下,Proxy所作的事情,無非是調用在不一樣的request時,調用觸發realSubject對應的方法;更抽象點看,Proxy所做的事情;在Java中 方法(Method)也是做爲一個對象來看待了,
動態代理工做的基本模式就是將本身的方法功能的實現交給 InvocationHandler角色,外界對Proxy角色中的每個方法的調用,Proxy角色都會交給InvocationHandler來處理,而InvocationHandler則調用具體對象角色的方法。以下圖所示:
在這種模式之中:代理Proxy 和RealSubject 應該實現相同的功能,這一點至關重要。(我這裏說的功能,能夠理解爲某個類的public方法)
在面向對象的編程之中,若是咱們想要約定Proxy 和RealSubject能夠實現相同的功能,有兩種方式:
a.一個比較直觀的方式,就是定義一個功能接口,而後讓Proxy 和RealSubject來實現這個接口。
b.還有比較隱晦的方式,就是經過繼承。由於若是Proxy 繼承自RealSubject,這樣Proxy則擁有了RealSubject的功能,Proxy還能夠經過重寫RealSubject中的方法,來實現多態。
其中JDK中提供的建立動態代理的機制,是以a 這種思路設計的,而cglib 則是以b思路設計的。
好比如今想爲RealSubject這個類建立一個動態代理對象,JDK主要會作如下工做:
1. 獲取 RealSubject上的全部接口列表;
2. 肯定要生成的代理類的類名,默認爲:com.sun.proxy.$ProxyXXXX ;3. 根據須要實現的接口信息,在代碼中動態建立 該Proxy類的字節碼;
4 . 將對應的字節碼轉換爲對應的class 對象;
5. 建立InvocationHandler 實例handler,用來處理Proxy全部方法調用;
6. Proxy 的class對象 以建立的handler對象爲參數,實例化一個proxy對象
JDK經過 java.lang.reflect.Proxy包來支持動態代理,通常狀況下,咱們使用下面的newProxyInstance方法
static Object |
newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 返回一個指定接口的代理類實例,該接口能夠將方法調用指派到指定的調用處理程序。 |
而對於InvocationHandler,咱們須要實現下列的invoke方法:
在調用代理對象中的每個方法時,在代碼內部,都是直接調用了InvocationHandler 的invoke方法,而invoke方法根據代理類傳遞給本身的method參數來區分是什麼方法。
Object |
invoke(Object proxy,Method method,Object[] args) 在代理實例上處理方法調用並返回結果。 |
講的有點抽象,下面經過一個實例來演示一下吧:
如今定義兩個接口Vehicle和Rechargable,Vehicle表示交通工具類,有drive()方法;Rechargable接口表示可充電的(工具),有recharge() 方法;
定義一個實現兩個接口的類ElectricCar,類圖以下:
經過下面的代碼片斷,來爲ElectricCar建立動態代理類:
package com.foo.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) { ElectricCar car = new ElectricCar(); // 1.獲取對應的ClassLoader ClassLoader classLoader = car.getClass().getClassLoader(); // 2.獲取ElectricCar 所實現的全部接口 Class[] interfaces = car.getClass().getInterfaces(); // 3.設置一個來自代理傳過來的方法調用請求處理器,處理全部的代理對象上的方法調用 InvocationHandler handler = new InvocationHandlerImpl(car); /* 4.根據上面提供的信息,建立代理對象 在這個過程當中, a.JDK會經過根據傳入的參數信息動態地在內存中建立和.class 文件等同的字節碼 b.而後根據相應的字節碼轉換成對應的class, c.而後調用newInstance()建立實例 */ Object o = Proxy.newProxyInstance(classLoader, interfaces, handler); Vehicle vehicle = (Vehicle) o; vehicle.drive(); Rechargable rechargeable = (Rechargable) o; rechargeable.recharge(); } }
生成動態代理類的字節碼而且保存到硬盤中:
JDK提供了sun.misc.ProxyGenerator.generateProxyClass(String proxyName,class[] interfaces) 底層方法來產生動態代理類的字節碼:
下面定義了一個工具類,用來將生成的動態代理類保存到硬盤中:
import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Proxy; import sun.misc.ProxyGenerator; public class ProxyUtils { /* * 將根據類信息 動態生成的二進制字節碼保存到硬盤中, * 默認的是clazz目錄下 * params :clazz 須要生成動態代理類的類 * proxyName : 爲動態生成的代理類的名稱 */ public static void generateClassFile(Class clazz,String proxyName) { //根據類信息和提供的代理類名稱,生成字節碼 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(); } } } }
仔細觀察能夠看出生成的動態代理類有如下特色:
1.繼承自 java.lang.reflect.Proxy,實現了 Rechargable,Vehicle 這兩個ElectricCar實現的接口;
2.類中的全部方法都是final 的;
3.全部的方法功能的實現都統一調用了InvocationHandler的invoke()方法。
cglib 生成動態代理類的機制----經過類繼承:
JDK中提供的生成動態代理類的機制有個鮮明的特色是: 某個類必須有實現的接口,而生成的代理類也只能代理某個類接口定義的方法,好比:若是上面例子的ElectricCar實現了繼承自兩個接口的方法外,另外實現了方法bee() ,則在產生的動態代理類中不會有這個方法了!更極端的狀況是:若是某個類沒有實現接口,那麼這個類就不能同JDK產生動態代理了!
幸虧咱們有cglib。「CGLIB(Code Generation Library),是一個強大的,高性能,高質量的Code生成類庫,它能夠在運行期擴展Java類與實現Java接口。」
cglib 建立某個類A的動態代理類的模式是:
1. 查找A上的全部非final 的public類型的方法定義;
2. 將這些方法的定義轉換成字節碼;
3. 將組成的字節碼轉換成相應的代理的class對象;
4. 實現 MethodInterceptor接口,用來處理 對代理類上全部方法的請求(這個接口和JDK動態代理InvocationHandler的功能和角色是同樣的)
我的總結:
- Java文件編譯完後以.class形式保存,即字節碼。JVM虛擬機讀取字節碼文件,取出二進制數據,加載到內存中,解析.class 文件內的信息,生成對應的 Class對象。
- 能夠經過classloader讀取.class文件的格式和結構,生成字節碼,動態建立一個類
代理模式
代理模式上,基本上有Subject角色,RealSubject角色,Proxy角色。其中:Subject角色負責定義RealSubject和Proxy角色應該實現的接口;RealSubject角色用來真正完成業務服務功能;Proxy角色負責將自身的Request請求,調用realsubject 對應的request功能來實現業務功能,本身不真正作業務。
靜態代理:靜態定義proxy類
當在代碼階段規定這種代理關係,Proxy類經過編譯器編譯成class文件,當系統運行時,此class已經存在了。這種靜態的代理模式當然在訪問沒法訪問的資源,加強現有的接口業務功能方面有很大的優勢,可是大量使用這種靜態代理,會使咱們系統內的類的規模增大,而且不易維護;而且因爲Proxy和RealSubject的功能 本質上是相同的,Proxy只是起到了中介的做用,這種代理在系統中的存在,致使系統結構比較臃腫和鬆散。
動態代理:
爲了解決這個問題,就有了動態地建立Proxy的想法:在運行狀態中,須要代理的地方,根據Subject 和RealSubject,動態地建立一個Proxy,用完以後,就會銷燬,這樣就能夠避免了Proxy 角色的class在系統中冗雜的問題了。
兩種方式:
a.一個比較直觀的方式,就是定義一個功能接口,而後讓Proxy 和RealSubject來實現這個接口。
b.還有比較隱晦的方式,就是經過繼承。由於若是Proxy 繼承自RealSubject,這樣Proxy則擁有了RealSubject的功能,Proxy還能夠經過重寫RealSubject中的方法,來實現多態。
經過接口建立:
1. 獲取 RealSubject上的全部接口列表;
2. 肯定要生成的代理類的類名,默認爲:com.sun.proxy.$ProxyXXXX ;3. 根據須要實現的接口信息,在代碼中動態建立 該Proxy類的字節碼;
4 . 將對應的字節碼轉換爲對應的class 對象;
5. 建立InvocationHandler 實例handler,用來處理Proxy全部方法調用;
6. Proxy 的class對象 以建立的handler對象爲參數,實例化一個proxy對象
InvocationHandler的做用:
在靜態代理中,代理Proxy中的方法,都指定了調用了特定的realSubject中的對應的方法:
在上面的靜態代理模式下,Proxy所作的事情,無非是調用在不一樣的request時,調用觸發realSubject對應的方法;更抽象點看,Proxy所做的事情;在Java中 方法(Method)也是做爲一個對象來看待了,
動態代理工做的基本模式就是將本身的方法功能的實現交給 InvocationHandler角色,外界對Proxy角色中的每個方法的調用,Proxy角色都會交給InvocationHandler來處理,而InvocationHandler則調用具體對象角色的方
JDK經過 java.lang.reflect.Proxy包來支持動態代理,通常狀況下,咱們使用下面的newProxyInstance方法
newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
返回一個指定接口的代理類實例,該接口能夠將方法調用指派到指定的調用處理程序。而對於InvocationHandler,咱們須要實現下列的invoke方法:
invoke(Object proxy,Method method,Object[] args)
在代理實例上處理方法調用並返回結果。在調用代理對象中的每個方法時,在代碼內部,都是直接調用了InvocationHandler 的invoke方法,而invoke方法根據代理類傳遞給本身的method參數來區分是什麼方法。
JDK提供了sun.misc.ProxyGenerator.generateProxyClass(String proxyName,class[] interfaces) 底層方法來產生動態代理類的字節碼: