Java 動態(tài)代理 原理解析

概要

AOP的攔截功能是由java中的動態(tài)代理來實現的琅束。說白了纯命,就是在目標類的基礎上增加切面邏輯争涌,生成增強的目標類(該切面邏輯或者在目標類函數執(zhí)行之前免胃,或者目標類函數執(zhí)行之后音五,或者在目標類函數拋出異常時候執(zhí)行。Spring中的動態(tài)代理是使用了JDK的動態(tài)代理和Cglib進行實現的羔沙。我們這里分析的是JDK中的動態(tài)代理實現機制躺涝。

下面我們通過例子快速了解JDK中的動態(tài)代理實現方式。

示例

需要代理的接口

public interface IHello {
    public void sayHello();
}

需要代理的類

public class HelloImpl implements IHello {
    public void sayHello() {
        System.out.println("Hello World...");
    }
}

調用處理器實現類

public class ProxyHandler implements InvocationHandler {
    private Object target;
    public ProxyHandler(Object target) {
        this.target = target;
    }
    public Object proxyInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("aspect before ... ");
        Object result = method.invoke(this.target, args);
        System.out.println("aspect after ... ");
        return result;
    }
}

測試類入口

public class Main {
    public static void main(String[] args) {
        ProxyHandler proxy = new ProxyHandler(new HelloImpl());
        IHello hello = (IHello) proxy.proxyInstance();
        hello.sayHello();
    }
}

Proxy 源碼解析

newProxyInstance() 方法

省略了不關心的代碼

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h) 
    throws IllegalArgumentException {
 
    Class<?> cl = getProxyClass0(loader, intfs);
    try {
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        cons.setAccessible(true);
        return cons.newInstance(new Object[]{h});
    } catch (Exception e) {
        ......
    } 
}
  1. 調用 getProxyClass0() 方法獲取代理類的 Class 對象
  2. 通過反射生成 代理類的實例扼雏,并返回坚嗜。

getProxyClass0() 方法

private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    return proxyClassCache.get(loader, interfaces);
}
  1. 檢查需要代理的類接口數量是否超過65535個夯膀,接口個數用2個byte存儲,最大支持65535個苍蔬。
  2. 把獲取代理類的Class對象委托給 proxyClassCache() 方法诱建。
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

proxyClassCache.get() 方法

  1. 首先通過 classLoader 獲取該classLoader下所有的代理類緩存map。
  2. 從該代理類緩存map中根據代理類所有實現的所有接口進行查找代理類碟绑。

如果都沒有找到對應的代理類俺猿,則使用ProxyClassFactory.apply() 方法生成代理類

ProxyClassFactory.apply()

  1. 生成代理類的報名和類名
  2. 通過ProxyGenerator.generateProxyClass() 方法動態(tài)生成代理類字節(jié)碼。
  3. 通過classloader 動態(tài)加載 字節(jié)碼格仲,并生成動態(tài)代理類的Class實例押袍,并返回

ProxyGenerator.generateProxyClass()

簡單的列出 生成字節(jié)碼的片段



添加默認的代理方法 hashCode、equals凯肋、toString 三個代理方法



循環(huán)迭代所有的接口伯病,把接口中所有的方法都生成代理方法。

生成字節(jié)碼:
從代碼中我們可以看到否过,有class文件中的 魔術頭、小版本號惭蟋、主版本號苗桂、類方法標志、當前類告组、超類煤伟、接口個數、每個實現接口木缝、屬性個數便锨、每個屬性、方法個數我碟、每個方法等信息放案。

動態(tài)生成代理類class文件

下面我們調用JDK內部提供的ProxyGenerator.generateProxyClass 方法把動態(tài)生成的代理類持久化到磁盤上。代碼如下:

public static void main(String[] args) {
    String name = "ProxyHello";
    byte[] data = ProxyGenerator.generateProxyClass(name, new Class[] { IHello.class });
    try {
        FileOutputStream out = new FileOutputStream("d:\\"+name + ".class");
        out.write(data);
        out.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

在D盤根目錄下生成ProxyHello.class文件矫俺。通過反編譯工具吱殉,我們得到如下代碼:

import cn.com.infcn.proxy.IHello;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class ProxyHello extends Proxy
  implements IHello
{
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m0;

  public ProxyHello(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }

  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  public final void sayHello()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("cn.com.infcn.proxy.IHello").getMethod("sayHello", new Class[0]);
      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());
  }
}

通過反編譯后的代碼我們可以看到,他內部包含4個代理方法:
equals厘托、toString友雳、hashCode 還有 IHello接口中的 sayHello 方法。

該代理類繼承了Proxy铅匹,并把我們創(chuàng)建的ProxyHandler 類通過構造方法自動注入到 代理類中押赊。

當我們調用代理類的sayHello方法時,sayHello方法就會調用我們實現的ProxyHandler 類中的invoke() 方法包斑,ProxyHandler.invoke() 方法調用我們真正的目標類的sayHello方法流礁。

想了解更多精彩內容請關注我的公眾號

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末涕俗,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子崇棠,更是在濱河造成了極大的恐慌咽袜,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枕稀,死亡現場離奇詭異询刹,居然都是意外死亡,警方通過查閱死者的電腦和手機萎坷,發(fā)現死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門凹联,熙熙樓的掌柜王于貴愁眉苦臉地迎上來雏掠,“玉大人句伶,你說我怎么就攤上這事芍瑞÷呈唬” “怎么了棒仍?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵师骗,是天一觀的道長屁擅。 經常有香客問我仲义,道長插佛,這世上最難降的妖魔是什么杠巡? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮雇寇,結果婚禮上氢拥,老公的妹妹穿的比我還像新娘。我一直安慰自己锨侯,他們只是感情好嫩海,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著囚痴,像睡著了一般叁怪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上深滚,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天骂束,我揣著相機與錄音,去河邊找鬼成箫。 笑死展箱,一個胖子當著我的面吹牛,可吹牛的內容都是我干的蹬昌。 我是一名探鬼主播混驰,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了栖榨?” 一聲冷哼從身側響起昆汹,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎婴栽,沒想到半個月后满粗,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡愚争,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年映皆,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片轰枝。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡捅彻,死狀恐怖,靈堂內的尸體忽然破棺而出鞍陨,到底是詐尸還是另有隱情步淹,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布诚撵,位于F島的核電站缭裆,受9級特大地震影響,放射性物質發(fā)生泄漏寿烟。R本人自食惡果不足惜澈驼,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望韧衣。 院中可真熱鬧,春花似錦购桑、人聲如沸畅铭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽硕噩。三九已至,卻和暖如春缭贡,著一層夾襖步出監(jiān)牢的瞬間炉擅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工阳惹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谍失,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓莹汤,卻偏偏與公主長得像快鱼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內容

  • title: Jdk動態(tài)代理原理解析 tags:代理 categories:筆記 date: 2017-06-14...
    行徑行閱讀 19,223評論 3 36
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理抹竹,服務發(fā)現线罕,斷路器,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • Java動態(tài)代理 引言 最近在看AOP代碼窃判,其中利用到了Java動態(tài)代理機制來實現AOP織入钞楼。所以好好地把Java...
    草捏子閱讀 1,519評論 0 18
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法袄琳,內部類的語法询件,繼承相關的語法,異常的語法跨蟹,線程的語...
    子非魚_t_閱讀 31,581評論 18 399
  • 這部片子王家衛(wèi)、梁朝偉痢艺、金城武仓洼、陳奕迅、杜鵑堤舒、張榕容色建、賈玲等等,如此黃金監(jiān)制加大牌卡司舌缤,在豆瓣上被猛批差評箕戳,有些人...
    文穴LCAVE閱讀 630評論 0 2