在正式說hibernate延遲加載時(shí),先說說一個(gè)比較奇怪的現(xiàn)象吧:hibernate中,在many-to-one時(shí),如果我們設(shè)置了延遲加載,會發(fā)現(xiàn)我們在eclipse的調(diào)試框中查看one對應(yīng)對象時(shí),它的內(nèi)部成員變量全是null的(因?yàn)檫@個(gè)原因我還調(diào)了好久的代碼!),貼張圖給你們感受下:

左邊是設(shè)置延遲加載的調(diào)試圖,右邊是沒設(shè)置延遲加載的示意圖;

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動畫培訓(xùn)                 平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動畫培訓(xùn)

ok,估計(jì)大家也理解我說什么了,下面就從這個(gè)現(xiàn)象進(jìn)作為入口,闡述hibernate延遲加載背后的原理----動態(tài)代理。

一、hibernate的延遲加載與動態(tài)代理

  1、hibernate中的延遲加載:get VS load

    我們知道,在hibernate方法中,直接涉及到延遲加載的方法有g(shù)et和load,使用get時(shí),不會延遲加載,load則反之。另外,在many-to-one等關(guān)系配置中,我們也可以通過lazy屬性設(shè)置是否延遲加載,這是我們對hibernate最直觀的認(rèn)識。

  2、現(xiàn)象解釋----動態(tài)代理機(jī)制

    所以,開頭說到的奇怪現(xiàn)象的原因是什么呢?其實(shí)在hibernate設(shè)置延遲加載后,hibernate返回給我們的對象(要延遲加載的對象)是一個(gè)代理對象,并不是真實(shí)的對象,該對象沒有真實(shí)對象的數(shù)據(jù),只有真正需要用到對象數(shù)據(jù)(調(diào)用getter等方法時(shí))時(shí),才會觸發(fā)hibernate去數(shù)據(jù)庫查對應(yīng)數(shù)據(jù),而且查回來的數(shù)據(jù)不會存儲在代理對象中,所以這些數(shù)據(jù)是無法在調(diào)試窗口查看到的。

    如果在調(diào)試是要查看該數(shù)據(jù),我們可以查看代理對象中的hadler屬性中的target變量,該對象變量才是真實(shí)的對象,看下面截圖:

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動畫培訓(xùn) 

 

 也就是說,我們user變量僅僅是一個(gè)代理類,target才是真正數(shù)據(jù)庫中獲取的數(shù)據(jù)。當(dāng)我們在調(diào)用getter方法式,hibernate會利用動態(tài)代理的方法,直接調(diào)用target中的getter方法發(fā)揮對應(yīng)的值。這樣也解釋了為什么hibernate可以延遲加載:通過代理類進(jìn)行加載時(shí)間的控制,在外界正真調(diào)用getter等方法操作數(shù)據(jù)時(shí)才會對相應(yīng)的方法進(jìn)行攔截,然后讀取數(shù)據(jù)庫。

 

二、動態(tài)代理原理

  上面也簡單介紹了hibernate延遲加載是通過動態(tài)代理實(shí)現(xiàn),所以上面是動態(tài)代理呢?

  1、理解代理的概念。

    代理是一個(gè)中間者,它的主要作用之一是我們可以利用代理對象來增強(qiáng)對真正對象的控制:例如在hibernate中控制數(shù)據(jù)加載的時(shí)間在正真調(diào)用數(shù)據(jù)時(shí)發(fā)生。具體的話,后面?zhèn)€人會寫一篇代理模式的博客簡單總結(jié)下代理模式,讀者也可以去查查代理模式以加深理解,這里不詳細(xì)講解。

    在jdk中的代理,主要通過一個(gè)叫做InvocationHandler的委托接口和Proxy的代理類來實(shí)現(xiàn)動態(tài)代理,一般來說,Proxy會通過調(diào)用InvocationHandler的invoke方法進(jìn)行代理委托:也就是invoke方法才是真正的代理方法,這個(gè)后面的代碼例子會詳細(xì)講解。所以,java中要動態(tài)代理的話,必須有一個(gè)InvocationHandler的具體實(shí)現(xiàn)類。

  2、java動態(tài)代理的詳細(xì)實(shí)現(xiàn)方式

       上面也提到了,java中動態(tài)代理中至少涉及三個(gè)對象:代理調(diào)用對象(參數(shù)代理實(shí)例),被代理對象,被委托的Handler對象即代理對象。下面就從三個(gè)對象,進(jìn)行一個(gè)簡單的動態(tài)代理實(shí)現(xiàn)

  首先,我們寫一個(gè)真實(shí)的類,該類要被代理對象代理。這里需要注意的是:java中的動態(tài)代理是只能支持接口的動態(tài)代理的,所以我們在實(shí)現(xiàn)具體類前必須抽象該類的方法,定義一個(gè)接口,至于為什么在java中只能支持接口動態(tài)代理,后面會詳細(xì)講解,下面貼上我的代碼,大家注意看注釋:

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動畫培訓(xùn)

/*
 * 首先定義一個(gè)接口被真實(shí)的類實(shí)現(xiàn),jdk中的動態(tài)代理只能代理接口類對象 */interface RealClassIfc{    public void method1(String myName);
}/*
 * 這個(gè)是真實(shí)的對象 */class RealClass implements RealClassIfc{    public void method1(String myName){
        System.out.println(this.getClass().getName() + " method1Name:" + myName);
    }
}

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動畫培訓(xùn)

  然后,我們定義一個(gè)代理類也就是InvocationHandler接口的具體實(shí)現(xiàn)類Handler,該類的對象對應(yīng)代理對象,具體的代碼說明在注釋中,請注意看:

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動畫培訓(xùn)

/*
 * 代理類,用于給jdk代理類Proxy進(jìn)行委托,該類需要實(shí)現(xiàn)一個(gè)接口InvocationHandler,該接口只有一個(gè)方法invoke */class ProxyClass implements InvocationHandler {    /*
     * 參數(shù)說明:
     * proxy:代理對象,該對象用于查詢代理對象的其他信息,更具體作用可以參考這篇博客:
     *        http://blog.csdn.net/bu2_int/article/details/60150319;
     * method:真實(shí)對象所對應(yīng)的方法
     * args:執(zhí)行上面method所需要的參數(shù)
     * 有需要該方法可以選擇返回值     */
    //真實(shí)對象,invoke方法中需要用到
    Object realClass = null;    public ProxyClass(Object realClass){        this.realClass = realClass;
    }    public Object invoke(Object proxy, Method method, Object[] args)            throws Throwable {        //在invoke方法體內(nèi)部執(zhí)行織入的代碼
        System.out.println("這個(gè)是RealClass method1執(zhí)行前要執(zhí)行的代碼");
        method.invoke(realClass,args);
        System.out.println("這個(gè)是RealClass method1執(zhí)行后要執(zhí)行的代碼");        return null;
    }
    
}

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動畫培訓(xùn)

 這里簡單說明下:InvacationHandler中只有一個(gè)方法(具體可以查看jdk源碼)invoke,而我們便是在該方法內(nèi)部進(jìn)行我們的代碼織入操作(這個(gè)可以聯(lián)系spring中的AOP思想,其實(shí)Spring AOP 的實(shí)現(xiàn)大體就是這樣,更詳細(xì)的AOP可以參考本人這篇博客)。所以,到這里我們可以大概猜到:hibernate的查詢操作大概就是在invoke方法中調(diào)用正式的getter等獲取數(shù)據(jù)方法前進(jìn)行的,動態(tài)代理幫我們攔截了getter等獲取數(shù)據(jù)方法并對應(yīng)地在這之前進(jìn)行了數(shù)據(jù)查詢等操作,具體可以參考查看hibernate源碼。

  然后,我們需要一個(gè)客戶端類來幫我們獲取代理實(shí)例,完成代理過程,請看下面代碼:

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動畫培訓(xùn)

    Object realClassInterface = 
    .realClassInterface =
     = = (RealClassIfc)Proxy.newProxyInstance(Handler..getClassLoader(), RealClass."動態(tài)代理的方法"

      //打印發(fā)現(xiàn)realClass的真實(shí)類是一個(gè)jre運(yùn)行時(shí)生成的一個(gè)代理類
      System.out.println(realClass.getClass().getName());

    }
}

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動畫培訓(xùn)

  一般來說,客戶端類不會直接調(diào)用InvocationHandler對象的invoke方法,而是通過jdk的Proxy類獲取一個(gè)代理對象實(shí)例(對應(yīng)上面的realClass對象),然后通過該對象來直接調(diào)用真實(shí)類的同名方法,這樣才能給調(diào)用者一個(gè)“我調(diào)用的是真實(shí)的類”,因?yàn)閷φ{(diào)用者而言,代理類應(yīng)當(dāng)是透明的。

  這里簡單說說Proxy的newProxyInstance方法的幾個(gè)參數(shù):

  第一個(gè)參數(shù)是代理類的加載器,代理對象通過該加載器來以反射的方式獲得委托的InvocationHadler具體實(shí)現(xiàn)類的對象,然后通過接口調(diào)用對應(yīng)invoke方法來實(shí)現(xiàn)真實(shí)類的方法調(diào)用,需要類加載器是因?yàn)镻roxy生成動態(tài)代理對象過程會生成一個(gè)描述代理類的字節(jié)碼,該字節(jié)碼加載時(shí)正需要一個(gè)classLoader;

  第二個(gè)參數(shù)是真實(shí)的類的所有接口信息,該信息給代理類有的作用:代理類會對應(yīng)“實(shí)現(xiàn)”真實(shí)類的所有接口(其實(shí)應(yīng)該是調(diào)用invoke方法+真實(shí)類對應(yīng)的方法),這樣也正是代理類的真正運(yùn)行機(jī)理:這種方法可以讓我們有機(jī)會在正真方法執(zhí)行前攔截到該方法,然后織入代碼,這也是動態(tài)代理實(shí)現(xiàn)AOP的大概原理,hibernate的延遲加載正是這樣實(shí)現(xiàn)的。從這里我們可以回答上面提出的問題:java中為什么只能通過接口實(shí)現(xiàn)動態(tài)代理了,這是因?yàn)镻roxy的newInstance方法限制的,更本質(zhì)的原因其實(shí)就是java不支持多繼承,所以代理對象不得不通過操作接口操作真實(shí)對象。

  第三個(gè)參數(shù)就是真正的代理類對象了,該對象才是正真的負(fù)責(zé)代理操作。

  注意的是,打印realClass的類名字可以得這樣結(jié)果:review.blog.hibernate.$Proxy0,并非是RealClass。從這里可以看出,jdk動態(tài)代理機(jī)制的確生成一個(gè)動態(tài)代理類字節(jié)碼,代理實(shí)例就是通過該字節(jié)碼對應(yīng)的類來創(chuàng)建的。更具體可以參考這個(gè)鏈接。

 

    動態(tài)代理是AOP實(shí)現(xiàn)的一種重要方式,通過InvocationHandler接口進(jìn)行方法的攔截并利用反射機(jī)制執(zhí)行一定的代碼正是AOP中織入代碼的重要手段,理解動態(tài)代理的原理對于我們理解更好的理解hibernate和spring等框架具有重要意義。  

   

http://www.cnblogs.com/lcplcpjava/p/6759859.html