在正式說hibernate延遲加載時,先說說一個比較奇怪的現象吧:hibernate中,在many-to-one時,若是咱們設置了延遲加載,會發現咱們在eclipse的調試框中查看one對應對象時,它的內部成員變量全是null的(由於這個緣由我還調了很久的代碼!),貼張圖給大家感覺下:html
左邊是設置延遲加載的調試圖,右邊是沒設置延遲加載的示意圖;java
ok,估計你們也理解我說什麼了,下面就從這個現象進做爲入口,闡述hibernate延遲加載背後的原理----動態代理。spring
1、hibernate的延遲加載與動態代理數據庫
一、hibernate中的延遲加載:get VS load框架
咱們知道,在hibernate方法中,直接涉及到延遲加載的方法有get和load,使用get時,不會延遲加載,load則反之。另外,在many-to-one等關係配置中,咱們也能夠經過lazy屬性設置是否延遲加載,這是咱們對hibernate最直觀的認識。eclipse
二、現象解釋----動態代理機制this
因此,開頭說到的奇怪現象的緣由是什麼呢?其實在hibernate設置延遲加載後,hibernate返回給咱們的對象(要延遲加載的對象)是一個代理對象,並非真實的對象,該對象沒有真實對象的數據,只有真正須要用到對象數據(調用getter等方法時)時,纔會觸發hibernate去數據庫查對應數據,並且查回來的數據不會存儲在代理對象中,因此這些數據是沒法在調試窗口查看到的。spa
若是在調試是要查看該數據,咱們能夠查看代理對象中的hadler屬性中的target變量,該對象變量纔是真實的對象,看下面截圖:.net
也就是說,咱們user變量僅僅是一個代理類,target纔是真正數據庫中獲取的數據。當咱們在調用getter方法式,hibernate會利用動態代理的方法,直接調用target中的getter方法發揮對應的值。這樣也解釋了爲何hibernate能夠延遲加載:經過代理類進行加載時間的控制,在外界正真調用getter等方法操做數據時纔會對相應的方法進行攔截,而後讀取數據庫。hibernate
2、動態代理原理
上面也簡單介紹了hibernate延遲加載是經過動態代理實現,因此上面是動態代理呢?
一、理解代理的概念。
代理是一箇中間者,它的主要做用之一是咱們能夠利用代理對象來加強對真正對象的控制:例如在hibernate中控制數據加載的時間在正真調用數據時發生。具體的話,後面我的會寫一篇代理模式的博客簡單總結下代理模式,讀者也能夠去查查代理模式以加深理解,這裏不詳細講解。
在jdk中的代理,主要經過一個叫作InvocationHandler的委託接口和Proxy的代理類來實現動態代理,通常來講,Proxy會經過調用InvocationHandler的invoke方法進行代理委託:也就是invoke方法纔是真正的代理方法,這個後面的代碼例子會詳細講解。因此,java中要動態代理的話,必須有一個InvocationHandler的具體實現類。
二、java動態代理的詳細實現方式
上面也提到了,java中動態代理中至少涉及三個對象:代理調用對象(參數代理實例),被代理對象,被委託的Handler對象即代理對象。下面就從三個對象,進行一個簡單的動態代理實現
首先,咱們寫一個真實的類,該類要被代理對象代理。這裏須要注意的是:java中的動態代理是隻能支持接口的動態代理的,因此咱們在實現具體類前必須抽象該類的方法,定義一個接口,至於爲何在java中只能支持接口動態代理,後面會詳細講解,下面貼上個人代碼,你們注意看註釋:
/* * 首先定義一個接口被真實的類實現,jdk中的動態代理只能代理接口類對象 */ interface RealClassIfc{ public void method1(String myName); } /* * 這個是真實的對象 */ class RealClass implements RealClassIfc{ public void method1(String myName){ System.out.println(this.getClass().getName() + " method1Name:" + myName); } }
而後,咱們定義一個代理類也就是InvocationHandler接口的具體實現類Handler,該類的對象對應代理對象,具體的代碼說明在註釋中,請注意看:
/* * 代理類,用於給jdk代理類Proxy進行委託,該類須要實現一個接口InvocationHandler,該接口只有一個方法invoke */ class ProxyClass implements InvocationHandler { /* * 參數說明: * proxy:代理對象,該對象用於查詢代理對象的其餘信息,更具體做用能夠參考這篇博客: * http://blog.csdn.net/bu2_int/article/details/60150319; * method:真實對象所對應的方法 * args:執行上面method所須要的參數 * 有須要該方法能夠選擇返回值 */ //真實對象,invoke方法中須要用到 Object realClass = null; public ProxyClass(Object realClass){ this.realClass = realClass; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //在invoke方法體內部執行織入的代碼 System.out.println("這個是RealClass method1執行前要執行的代碼"); method.invoke(realClass,args); System.out.println("這個是RealClass method1執行後要執行的代碼"); return null; } }
這裏簡單說明下:InvacationHandler中只有一個方法(具體能夠查看jdk源碼)invoke,而咱們即是在該方法內部進行咱們的代碼織入操做(這個能夠聯繫spring中的AOP思想,其實Spring AOP 的實現大致就是這樣,更詳細的AOP能夠參考本人這篇博客)。因此,到這裏咱們能夠大概猜到:hibernate的查詢操做大概就是在invoke方法中調用正式的getter等獲取數據方法前進行的,動態代理幫咱們攔截了getter等獲取數據方法並對應地在這以前進行了數據查詢等操做,具體能夠參考查看hibernate源碼。
而後,咱們須要一個客戶端類來幫咱們獲取代理實例,完成代理過程,請看下面代碼:
/* * 這個是客戶端類,在hibernate中,若是延遲加載被設置了,咱們獲取的對象只是代理對象,就是對應這個類的 * 該類會經過jdk 的Proxy類的getInstance方法獲取一個代理類,該代理類會自動幫你實現真實類的全部接口對應方法 */ class ClientClass { //該屬性是真實類 Object realClassInterface = null; //在構造代理類時初始化該類 public ClientClass(Object realClassInterface){ this.realClassInterface = realClassInterface; } //獲取代理實例方法,該方法用於獲取代理實例 public void proxyMethod(){ InvocationHandler handler = new Handler(realClassInterface); RealClassIfc realClass = (RealClassIfc)Proxy.newProxyInstance(Handler.class.getClassLoader(), RealClass.class.getInterfaces(), handler); realClass.method1("動態代理的方法");
//打印發現realClass的真實類是一個jre運行時生成的一個代理類
System.out.println(realClass.getClass().getName());
}
}
通常來講,客戶端類不會直接調用InvocationHandler對象的invoke方法,而是經過jdk的Proxy類獲取一個代理對象實例(對應上面的realClass對象),而後經過該對象來直接調用真實類的同名方法,這樣才能給調用者一個「我調用的是真實的類」,由於對調用者而言,代理類應當是透明的。
這裏簡單說說Proxy的newProxyInstance方法的幾個參數:
第一個參數是代理類的加載器,代理對象經過該加載器來以反射的方式得到委託的InvocationHadler具體實現類的對象,而後經過接口調用對應invoke方法來實現真實類的方法調用,須要類加載器是由於Proxy生成動態代理對象過程會生成一個描述代理類的字節碼,該字節碼加載時正須要一個classLoader;
第二個參數是真實的類的全部接口信息,該信息給代理類有的做用:代理類會對應「實現」真實類的全部接口(其實應該是調用invoke方法+真實類對應的方法),這樣也正是代理類的真正運行機理:這種方法可讓咱們有機會在正真方法執行前攔截到該方法,而後織入代碼,這也是動態代理實現AOP的大概原理,hibernate的延遲加載正是這樣實現的。從這裏咱們能夠回答上面提出的問題:java中爲何只能經過接口實現動態代理了,這是由於Proxy的newInstance方法限制的,更本質的緣由其實就是java不支持多繼承,因此代理對象不得不經過操做接口操做真實對象。
第三個參數就是真正的代理類對象了,該對象纔是正真的負責代理操做。
注意的是,打印realClass的類名字能夠得這樣結果:review.blog.hibernate.$Proxy0,並不是是RealClass。從這裏能夠看出,jdk動態代理機制的確生成一個動態代理類字節碼,代理實例就是經過該字節碼對應的類來建立的。更具體能夠參考這個連接。
動態代理是AOP實現的一種重要方式,經過InvocationHandler接口進行方法的攔截並利用反射機制執行必定的代碼正是AOP中織入代碼的重要手段,理解動態代理的原理對於咱們理解更好的理解hibernate和spring等框架具備重要意義。