最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java兩種動態(tài)代理JDK動態(tài)代理和CGLIB動態(tài)代理詳解

 更新時間:2023年11月28日 10:08:23   作者:ZhaoJuFei  
這篇文章主要介紹了Java兩種動態(tài)代理JDK動態(tài)代理和CGLIB動態(tài)代理詳解,代理模式是23種設計模式的一種,他是指一個對象A通過持有另一個對象B,可以具有B同樣的行為的模式,為了對外開放協(xié)議,B往往實現(xiàn)了一個接口,A也會去實現(xiàn)接口,需要的朋友可以參考下

代理模式

代理模式是23種設計模式的一種,他是指一個對象A通過持有另一個對象B,可以具有B同樣的行為的模式。為了對外開放協(xié)議,B往往實現(xiàn)了一個接口,A也會去實現(xiàn)接口。但是B是“真正”實現(xiàn)類,A則比較“虛”,他借用了B的方法去實現(xiàn)接口的方法。A雖然是“偽軍”,但它可以增強B,在調(diào)用B的方法前后都做些其他的事情。Spring AOP就是使用了動態(tài)代理完成了代碼的動態(tài)“織入”。

使用代理好處還不止這些,一個工程如果依賴另一個工程給的接口,但是另一個工程的接口不穩(wěn)定,經(jīng)常變更協(xié)議,就可以使用一個代理,接口變更時,只需要修改代理,不需要一一修改業(yè)務代碼。從這個意義上說,所有調(diào)外界的接口,我們都可以這么做,不讓外界的代碼對我們的代碼有侵入,這叫防御式編程。代理其他的應用可能還有很多。

上述例子中,類A寫死持有B,就是B的靜態(tài)代理。如果A代理的對象是不確定的,就是動態(tài)代理。動態(tài)代理目前有兩種常見的實現(xiàn),jdk動態(tài)代理和cglib動態(tài)代理。

JDK動態(tài)代理

jdk動態(tài)代理是jre提供給我們的類庫,可以直接使用,不依賴第三方。先看下jdk動態(tài)代理的使用代碼,再理解原理。

首先有個“明星”接口類,有唱、跳兩個功能:

package proxy;
 
public interface Star
{
    String sing(String name);
    
    String dance(String name);
}

再有個明星實現(xiàn)類“劉德華”:

package proxy;
 
public class LiuDeHua implements Star
{   
    @Override
    public String sing(String name)
    {
         System.out.println("給我一杯忘情水");
 
        return "唱完" ;
    }
    
    @Override
    public String dance(String name)
    {
        System.out.println("開心的馬騮");
 
        return "跳完" ;
    }
}

明星演出前需要有人收錢,由于要準備演出,自己不做這個工作,一般交給一個經(jīng)紀人。便于理解,它的名字以Proxy結(jié)尾,但他不是代理類,原因是它沒有實現(xiàn)我們的明星接口,無法對外服務,它僅僅是一個wrapper。

package proxy;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
 
public class StarProxy implements InvocationHandler
{
    // 目標類,也就是被代理對象
    private Object target;
    
    public void setTarget(Object target)
    {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        // 這里可以做增強
        System.out.println("收錢");
        
        Object result = method.invoke(target, args);
        
        return result;
    }
    
    // 生成代理類
    public Object CreatProxyedObj()
    {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }  
   
}

上述例子中,方法CreatProxyedObj返回的對象才是我們的代理類,它需要三個參數(shù),前兩個參數(shù)的意思是在同一個classloader下通過接口創(chuàng)建出一個對象,該對象需要一個屬性,也就是第三個參數(shù),它是一個InvocationHandler。需要注意的是這個CreatProxyedObj方法不一定非得在我們的StarProxy類中,往往放在一個工廠類中。上述代理的代碼使用過程一般如下:

1、new一個目標對象

2、new一個InvocationHandler,將目標對象set進去

3、通過CreatProxyedObj創(chuàng)建代理對象,強轉(zhuǎn)為目標對象的接口類型即可使用,實際上生成的代理對象實現(xiàn)了目標接口。

        Star ldh = new LiuDeHua();
 
        StarProxy proxy = new StarProxy();
 
        proxy.setTarget(ldh); 
  
        Object obj = proxy.CreatProxyedObj();
        
        Star star = (Star)obj;

Proxy(jdk類庫提供)根據(jù)B的接口生成一個實現(xiàn)類,我們成為C,它就是動態(tài)代理類(該類型是 $Proxy+數(shù)字 的“新的類型”)。生成過程是:由于拿到了接口,便可以獲知接口的所有信息(主要是方法的定義),也就能聲明一個新的類型去實現(xiàn)該接口的所有方法,這些方法顯然都是“虛”的,它調(diào)用另一個對象的方法。當然這個被調(diào)用的對象不能是對象B,如果是對象B,我們就沒法增強了,等于饒了一圈又回來了。

所以它調(diào)用的是B的包裝類,這個包裝類需要我們來實現(xiàn),但是jdk給出了約束,它必須實現(xiàn)InvocationHandler,上述例子中就是StarProxy, 這個接口里面有個方法,它是所有Target的所有方法的調(diào)用入口(invoke),調(diào)用之前我們可以加自己的代碼增強。

看下我們的實現(xiàn),我們在InvocationHandler里調(diào)用了對象B(target)的方法,調(diào)用之前增強了B的方法。

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        // 這里增強
        System.out.println("收錢");
        
        Object result = method.invoke(target, args);
        
        return result;
    }

所以可以這么認為C代理了InvocationHandler,InvocationHandler代理了我們的類B,兩級代理。

整個JDK動態(tài)代理的秘密也就這些,簡單一句話,動態(tài)代理就是要生成一個包裝類對象,由于代理的對象是動態(tài)的,所以叫動態(tài)代理。由于我們需要增強,這個增強是需要留給開發(fā)人員開發(fā)代碼的,因此代理類不能直接包含被代理對象,而是一個InvocationHandler,該InvocationHandler包含被代理對象,并負責分發(fā)請求給被代理對象,分發(fā)前后均可以做增強。從原理可以看出,JDK動態(tài)代理是“對象”的代理。

下面看下動態(tài)代理類到底如何調(diào)用的InvocationHandler的,為什么InvocationHandler的一個invoke方法能為分發(fā)target的所有方法。C中的部分代碼示例如下,通過反編譯生成后的代碼查看,摘自鏈接地址。Proxy創(chuàng)造的C是自己(Proxy)的子類,且實現(xiàn)了B的接口,一般都是這么修飾的:

public final class XXX extends Proxy implements XXX

一個方法代碼如下:

 
  public final String SayHello(String paramString)
  {
    try
    {
      return (String)this.h.invoke(this, m4, new Object[] { paramString });
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }

可以看到,C中的方法全部通過調(diào)用h實現(xiàn),其中h就是InvocationHandler,是我們在生成C時傳遞的第三個參數(shù)。這里還有個關(guān)鍵就是SayHello方法(業(yè)務方法)跟調(diào)用invoke方法時傳遞的參數(shù)m4一定要是一一對應的,但是這些對我們來說都是透明的,由Proxy在newProxyInstance時保證的。留心看到C在invoke時把自己this傳遞了過去,InvocationHandler的invoke的第一個方法也就是我們的動態(tài)代理實例類,業(yè)務上有需要就可以使用它。(所以千萬不要在invoke方法里把請求分發(fā)給第一個參數(shù),否則很明顯就死循環(huán)了)

C類中有B中所有方法的成員變量

  private static Method m1;
  private static Method m3;
  private static Method m4;
  private static Method m2;
  private static Method m0;

這些變量在static靜態(tài)代碼塊初始化,這些變量是在調(diào)用invocationhander時必要的入?yún)?,也讓我們依稀看到Proxy在生成C時留下的痕跡。

static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("jiankunking.Subject").getMethod("SayGoodBye", new Class[0]);
      m4 = Class.forName("jiankunking.Subject").getMethod("SayHello", new Class[] { Class.forName("java.lang.String") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }

從以上分析來看,要想徹底理解一個東西,再多的理論不如看源碼,底層的原理非常重要。

jdk動態(tài)代理類圖如下

cglib動態(tài)代理

我們了解到,“代理”的目的是構(gòu)造一個和被代理的對象有同樣行為的對象,一個對象的行為是在類中定義的,對象只是類的實例。所以構(gòu)造代理,不一定非得通過持有、包裝對象這一種方式。

通過“繼承”可以繼承父類所有的公開方法,然后可以重寫這些方法,在重寫時對這些方法增強,這就是cglib的思想。根據(jù)里氏代換原則(LSP),父類需要出現(xiàn)的地方,子類可以出現(xiàn),所以cglib實現(xiàn)的代理也是可以被正常使用的。

先看下代碼

package proxy;
 
import java.lang.reflect.Method;
 
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
 
public class CglibProxy implements MethodInterceptor
{
    // 根據(jù)一個類型產(chǎn)生代理類,此方法不要求一定放在MethodInterceptor中
    public Object CreatProxyedObj(Class<?> clazz)
    {
        Enhancer enhancer = new Enhancer();
        
        enhancer.setSuperclass(clazz);
        
        enhancer.setCallback(this);
        
        return enhancer.create();
    }
    
    @Override
    public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
    {
        // 這里增強
        System.out.println("收錢");
        
        return arg3.invokeSuper(arg0, arg2);
    } 
}

從代碼可以看出,它和jdk動態(tài)代理有所不同,對外表現(xiàn)上看CreatProxyedObj,它只需要一個類型clazz就可以產(chǎn)生一個代理對象, 所以說是“類的代理”,且創(chuàng)造的對象通過打印類型發(fā)現(xiàn)也是一個新的類型。不同于jdk動態(tài)代理,jdk動態(tài)代理要求對象必須實現(xiàn)接口(三個參數(shù)的第二個參數(shù)),cglib對此沒有要求。

cglib的原理是這樣,它生成一個繼承B的類型C(代理類),這個代理類持有一個MethodInterceptor,我們setCallback時傳入的。 C重寫所有B中的方法(方法名一致),然后在C中,構(gòu)建名叫“CGLIB”+“$父類方法名$”的方法(下面叫cglib方法,所有非private的方法都會被構(gòu)建),方法體里只有一句話super.方法名(),可以簡單的認為保持了對父類方法的一個引用,方便調(diào)用。

這樣的話,C中就有了重寫方法、cglib方法、父類方法(不可見),還有一個統(tǒng)一的攔截方法(增強方法intercept)。其中重寫方法和cglib方法肯定是有映射關(guān)系的。

C的重寫方法是外界調(diào)用的入口(LSP原則),它調(diào)用MethodInterceptor的intercept方法,調(diào)用時會傳遞四個參數(shù),第一個參數(shù)傳遞的是this,代表代理類本身,第二個參數(shù)標示攔截的方法,第三個參數(shù)是入?yún)ⅲ谒膫€參數(shù)是cglib方法,intercept方法完成增強后,我們調(diào)用cglib方法間接調(diào)用父類方法完成整個方法鏈的調(diào)用。

這里有個疑問就是intercept的四個參數(shù),為什么我們使用的是arg3而不是arg1?

    @Override
    public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
    {
        System.out.println("收錢");
        
        return arg3.invokeSuper(arg0, arg2);
    }

 因為如果我們通過反射 arg1.invoke(arg0, ...)這種方式是無法調(diào)用到父類的方法的,子類有方法重寫,隱藏了父類的方法,父類的方法已經(jīng)不可見,如果硬調(diào)arg1.invoke(arg0, ...)很明顯會死循環(huán)。

所以調(diào)用的是cglib開頭的方法,但是,我們使用arg3也不是簡單的invoke,而是用的invokeSuper方法,這是因為cglib采用了fastclass機制,不僅巧妙的避開了調(diào)不到父類方法的問題,還加速了方法的調(diào)用。

fastclass基本原理是,給每個方法編號,通過編號找到方法執(zhí)行避免了通過反射調(diào)用。

對比JDK動態(tài)代理,cglib依然需要一個第三者分發(fā)請求,只不過jdk動態(tài)代理分發(fā)給了目標對象,cglib最終分發(fā)給了自己,通過給method編號完成調(diào)用。cglib是繼承的極致發(fā)揮,本身還是很簡單的,只是fastclass需要另行理解。

測試

   public static void main(String[] args)
    {
        int times = 1000000;
        
        Star ldh = new LiuDeHua();
        StarProxy proxy = new StarProxy();
        proxy.setTarget(ldh);
        
        long time1 = System.currentTimeMillis();
        Star star = (Star)proxy.CreatProxyedObj();
        long time2 = System.currentTimeMillis();
        System.out.println("jdk創(chuàng)建時間:" + (time2 - time1));
        
        CglibProxy proxy2 = new CglibProxy();
        long time5 = System.currentTimeMillis();
        Star star2 = (Star)proxy2.CreatProxyedObj(LiuDeHua.class);
        long time6 = System.currentTimeMillis();
        System.out.println("cglib創(chuàng)建時間:" + (time6 - time5));
        
        long time3 = System.currentTimeMillis();
        for (int i = 1; i <= times; i++)
        {
            star.sing("ss");
            
            star.dance("ss");
        }
        long time4 = System.currentTimeMillis();
        System.out.println("jdk執(zhí)行時間" + (time4 - time3));
        
        long time7 = System.currentTimeMillis();
        for (int i = 1; i <= times; i++)
        {
            star2.sing("ss");
            
            star2.dance("ss");
        }
        
        long time8 = System.currentTimeMillis();
        
        System.out.println("cglib執(zhí)行時間" + (time8 - time7));   
    }

經(jīng)測試,jdk創(chuàng)建對象的速度遠大于cglib,這是由于cglib創(chuàng)建對象時需要操作字節(jié)碼。cglib執(zhí)行速度略大于jdk,所以比較適合單例模式。另外由于CGLIB的大部分類是直接對Java字節(jié)碼進行操作,這樣生成的類會在Java的永久堆中。如果動態(tài)代理操作過多,容易造成永久堆滿,觸發(fā)OutOfMemory異常。spring默認使用jdk動態(tài)代理,如果類沒有接口,則使用cglib。

到此這篇關(guān)于Java兩種動態(tài)代理JDK動態(tài)代理和CGLIB動態(tài)代理詳解的文章就介紹到這了,更多相關(guān)Java動態(tài)代理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring Boot中如何使用Convert接口實現(xiàn)類型轉(zhuǎn)換器

    Spring Boot中如何使用Convert接口實現(xiàn)類型轉(zhuǎn)換器

    這篇文章主要介紹了Spring Boot中使用Convert接口實現(xiàn)類型轉(zhuǎn)換器的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • SpringBoot項目中如何解決跨域問題的最新方案?

    SpringBoot項目中如何解決跨域問題的最新方案?

    跨域問題是瀏覽器為了保護用戶的信息安全,實施了同源策略(Same-Origin Policy),即只允許頁面請求同源(相同協(xié)議、域名和端口)的資源,當 JavaScript 發(fā)起的請求跨越了同源策略,即請求的目標與當前頁面的域名、端口、協(xié)議不一致時,瀏覽器會阻止請求的發(fā)送或接收
    2025-03-03
  • Spring配置文件的超詳細圖文介紹

    Spring配置文件的超詳細圖文介紹

    Spring配置文件是一個或多個標準的XML文檔,下面這篇文章主要給大家介紹了關(guān)于Spring配置文件的超詳細圖文介紹,文中通過圖文以及實例代碼介紹的非常詳細,需要的朋友可以參考下
    2023-06-06
  • java的url方式、本地方式獲取json文件內(nèi)容

    java的url方式、本地方式獲取json文件內(nèi)容

    這篇文章給大家分享了java的url方式、本地方式獲取json文件內(nèi)容的實例代碼,有需要的朋友參考學習下。
    2018-07-07
  • spring boot jpa寫原生sql報Cannot resolve table錯誤解決方法

    spring boot jpa寫原生sql報Cannot resolve table錯誤解決方法

    在本篇文章里小編給大家整理的是關(guān)于spring boot jpa寫原生sql報Cannot resolve table錯誤的解決方法,需要的朋友學習下。
    2019-11-11
  • java利用JEXL實現(xiàn)動態(tài)表達式編譯

    java利用JEXL實現(xiàn)動態(tài)表達式編譯

    這篇文章主要介紹了java利用JEXL實現(xiàn)動態(tài)表達式編譯,系統(tǒng)要獲取多個數(shù)據(jù)源的數(shù)據(jù),并進行處理,最后輸出多個字段。字段的計算規(guī)則一般是簡單的取值最多加一點條件判斷,下面是具體的實現(xiàn)方法
    2021-04-04
  • 淺談JVM垃圾回收之哪些對象可以被回收

    淺談JVM垃圾回收之哪些對象可以被回收

    這篇文章主要介紹了JVM垃圾回收之哪些對象可以被回收,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-03-03
  • Spring配置文件無法讀取properties屬性的解決

    Spring配置文件無法讀取properties屬性的解決

    這篇文章主要介紹了Spring配置文件無法讀取properties屬性的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • Java編程實現(xiàn)逆波蘭表達式代碼示例

    Java編程實現(xiàn)逆波蘭表達式代碼示例

    本文主要對Java算法逆波蘭表達式的相關(guān)內(nèi)容作了介紹,涉及逆波蘭表達式的定義已經(jīng)在Java中的實現(xiàn),具有一定參考價值,需要的朋友可以了解下。
    2017-10-10
  • 在IDEA中如何設置最多顯示文件標簽個數(shù)

    在IDEA中如何設置最多顯示文件標簽個數(shù)

    在使用IDEA進行編程時,可能會同時打開多個文件,當文件過多時,文件標簽會占據(jù)大部分的IDEA界面,影響我們的編程效率,因此,我們可以通過設置IDEA的文件標簽顯示個數(shù),來優(yōu)化我們的編程環(huán)境,具體的設置方法如下
    2024-10-10

最新評論

抚顺市| 桃源县| 新建县| 彝良县| 普安县| 博客| 深圳市| 万荣县| 湘潭市| 观塘区| 长沙市| 绥宁县| 红安县| 潞西市| 天镇县| 邢台市| 东丽区| 侯马市| 宁武县| 青铜峡市| 定南县| 静安区| 抚宁县| 天镇县| 黎城县| 墨竹工卡县| 石嘴山市| 巴青县| 横峰县| 多伦县| 綦江县| 张家港市| 侯马市| 理塘县| 永定县| 临夏市| 疏附县| 永善县| 建湖县| 金阳县| 清河县|