JDK動態(tài)代理深入探究

在上一篇文章設計模式之代理模式里,我們說了JDK動態(tài)代理類,不過我相信好多人都有疑惑,包括我,剛開始學的時候都是一臉懵逼,那么到底疑惑在哪里呢?

我的疑惑就是這個InvocationHandler的invoke方法到底是有啥用?我們都沒有調(diào)用它。newProxyInstance返回的東西到底是啥?等等,這篇文章我們就一起來探討一下吧。


首先我們先寫一個簡單的動態(tài)代理吧,有例子更好說明。還是買車的例子,哈哈,說明我挺想買車的。我就直接復制上篇文章的。

// Car.java
package com.codeliu.dao;

public interface Car {
    // vip客戶可以直接買車
    public void buyCar();
}
// CarImp1.java
package com.codeliu.dao.impl;
import com.codeliu.dao.Car;
/**
 * 委托類1
 */
public class CarImp1 implements Car {
    public void buyCar() {
        System.out.println("我是vip1");
    }
}
// CarProxy.java
package com.codeliu.dao.impl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 動態(tài)代理類
 */
public class CarProxy implements InvocationHandler {
    private Object target;
    /**
     * 綁定一個委托對象并獲得一個代理類對象
     * @param target [description]
     * @return [description]
     */
    public Object bind(Object target) {
        this.target = target;
        // 取得代理對象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 
           this);
    }
    @Override
    //這個方法并不是我們自己去調(diào)用
    public Object invoke(Object proxy, Method method, Object[] args)
       throws Throwable {
        System.out.println("調(diào)用這個方法前:" + method);
        // 執(zhí)行委托類的方法
        Object result = method.invoke(target,args);
        System.out.println("調(diào)用這個方法后:" + method);
        return result;
    }
}
package com.codeliu.test;

import com.codeliu.dao.impl.CarImp1;
import com.codeliu.dao.impl.CarImp2;
import com.codeliu.dao.impl.CarProxy;
import com.codeliu.dao.Car;
public class TestProxy {
    public static void main(String[] args) {
        CarProxy cp = new CarProxy();
        // 傳入一個實現(xiàn)了該接口的實例就行
        Car car = (Car)cp.bind(new CarImp1());
        // Car car = (Car)cp.bind(new CarImp2());
        car.buyCar();
    }
}

好了,那么現(xiàn)在我們就來看看Test.java里生成的car對象到底是個什么東a西,是我們的Car接口嗎?(我一開始一直理解不通)

我們利用反射可以獲取到car對應類的大部分信息,好了,現(xiàn)在我們把Test.java改成如下形式

package com.codeliu.test;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.codeliu.dao.Car;
import com.codeliu.dao.impl.CarImp1;
import com.codeliu.dao.impl.CarProxy;

public class Test {
    public static void main(String[] args) {
        CarProxy cp = new CarProxy();
        // 傳入一個實現(xiàn)了該接口的實例就行
        Car car = (Car)cp.bind(new CarImp1());
        // Car car = (Car)cp.bind(new CarImp2());
        car.buyCar();
        
        System.out.println("car是Proxy類的子類嗎?" + (car instanceof Proxy));
        Class<?> c = car.getClass();
        System.out.println(c);
        System.out.println("car的Class類是:" + c.getName());
        Field[] fields = c.getDeclaredFields();
        System.out.print("car的Class類中有哪些字段:");
        for(Field f:fields) {
            System.out.print(f.getName() + "   ");
        }
        System.out.println();
        System.out.print("car的Class類中有哪些方法:");
        Method[] methods = c.getDeclaredMethods();
        for(Method m:methods) {
            System.out.print(m.getName() + "   ");
        }
        System.out.println();
        System.out.println("car的Class類的父類是:" + c.getSuperclass());
        
        Class<?>[] class1 = c.getInterfaces();
        System.out.print("car的Class類實現(xiàn)的接口有:");
        for(Class<?> c1:class1) {
            System.out.println(c1.getName() + "  ");
        }
    }
}

運行一下,輸出如下

調(diào)用這個方法前:public abstract void com.codeliu.dao.Car.buyCar()
我是vip1
調(diào)用這個方法后:public abstract void com.codeliu.dao.Car.buyCar()
car是Proxy類的子類嗎?true
class com.sun.proxy.$Proxy0
car的Class類是:com.sun.proxy.$Proxy0
car的Class類中有哪些字段:m1   m2   m3   m0   
car的Class類中有哪些方法:equals   toString   hashCode   buyCar   
car的Class類的父類是:class java.lang.reflect.Proxy
car的Class類實現(xiàn)的接口有:com.codeliu.dao.Car  

恩,發(fā)現(xiàn)了什么,這個玩意竟然是一個Proxy類的子類,它的名字是$Proxy0,實現(xiàn)了我們自己定義的Car接口,還有四個字段和方法。



既然知道了上面的結(jié)果,那么我們就去看看newProxyInstance方法的源碼,應該能找到答案了。

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);
}

我就截了比較重要的,getProxyClass0方法,它的作用就是生成一個proxy類,好,我們看看這個方法。

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

再去看看這個get方法,emmmmm,代碼太多了,尼瑪,我也看不太懂,不過經(jīng)過我查資料,總結(jié)如下

newProxyInstance方法調(diào)用getProxyClass0生成一個$Proxy0類。
恩沒了,現(xiàn)在我們要找答案就得去看看$Proxy0類的源碼了。

因為生成的是一個.class文件,所以我們先反編譯后,可以看到源碼如下

package com.sun.proxy;

import com.codeliu.dao.Car;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0
  extends Proxy
  implements Car
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  
  public $Proxy0(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject)
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final String toString()
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final void buyCar()
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final int hashCode()
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("com.codeliu.dao.Car").getMethod("buyCar", 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());
    }
  }
}

$Proxy0繼承了Proxy類并實現(xiàn)了Car接口,它有一個構(gòu)造方法,傳入了一個InvocationHandler實例并調(diào)用了父類的構(gòu)造方法,然后是四個final方法,其他三個是Object類傳過來的,還有一個叫buyCar,這不就是我們在接口中定義的嗎?我們看看它怎么寫的

public final void buyCar()
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

恩,首先m3在靜態(tài)塊中,取得Car接口中的buyCar方法,然后是調(diào)用invoke方法,666,現(xiàn)在終于解決了文章開頭的疑惑,原來在這里調(diào)用了,等等,那么這個h又是什么東西,我們看看Proxy的源碼

protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
}

一個字段和一個帶參的構(gòu)造函數(shù),我們再看看$Proxy0的源碼中有這么一段

public $Proxy0(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }

好家伙,到這里就真相大白了,雖然我還是不知道這個$Proxy0.class怎么生出來的,但基本也懂了,再總結(jié)一下整趟流程。

首先是我們寫了一個接口,一個委托類實現(xiàn)了這個接口,然后寫一個代理類實現(xiàn)InvocationHandler接口并重寫invoke方法,之后調(diào)用Proxy的newProxyInstance方法,這個方法調(diào)用getProxyClass0方法生成一個動態(tài)代理對象,這個對象對應的類是$Proxy0,$Proxy0繼承了Proxy類并且實現(xiàn)了我們自己寫的接口,在這個類中,有靜態(tài)字段和final修飾的方法,其中有我們在接口中定義的方法(為了好表述,我先把它叫做method),通過method方法,調(diào)用我們在代理類中重寫的invoke,而我們在invoke方法中,又調(diào)用了委托類的方法,所以表面上好像是直接調(diào)用了委托類的方法,其實繞了一大圈,是系統(tǒng)幫我們調(diào)用了。



不知道大家有沒有看懂,沒有看懂就多看幾遍,因為我說的也有點蒙圈了。今天連續(xù)寫了兩篇文章,好餓啊,不過弄懂了困擾我的問題,賊開心。

我是一個追求完美的人啊,我好想畫一個流程圖啊,不然一大段文字,不形象又難懂,不過我沒力氣了,有沒有好心人畫一個啊。==

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • #我想你快樂# 00:06整個世界都是安靜的,一個人窩在寢室的床上玩手機,除了室友偶爾的翻身聲和窗外汽車的呼嘯聲...
    馬滾滾閱讀 314評論 0 0
  • 2.3性能:準確的元數(shù)據(jù) 指南2.3.7 - 性能 - 精準元 在App Store上所顯示的程序名稱或字幕包括關...
    景彧閱讀 223評論 0 0
  • 很慶幸自己出生和成長在北方一個城市的一所院落,總以為家的樣子是因為有屋有院才規(guī)矩方正延伸圓滿。 尤其明顯的是在過年...
    紫飛魚21閱讀 401評論 0 0
  • 什么樣的小工具屬于剛需?這樣的剛需如何盈利?這樣的需求其實是最適合我們業(yè)余時間去做的。 這次同樣的也是通過百度搜索...
    Zero雜談閱讀 1,644評論 0 0
  • 今天是我開始寫日記的第一天,我久久沒有落筆,因為我不知道寫些什么?我有時候在想我的工作很自由,怎么每天時間還是這么...
    劉梓涵媽媽閱讀 234評論 0 0

友情鏈接更多精彩內(nèi)容