看過Jdk動態代理類長啥樣嗎?Jdk動態代理原理原始碼一本到

小之Evan發表於2019-04-07

哈嘍呀~~筒子們,小之最近在看Spring的原始碼,正好遇到幾個Aop的問題涉及到Java的動態代理,之前對這個東西大致能理解,但是沒有仔細的去看原始碼,今天我們來扒一扒它的真面目。

閱讀本文,你將會收穫:

  • 理解為什麼Jdk動態代理,代理類必須實現介面

  • 理解Jdk動態代理全過程

  • 看到Jdk的動態代理類的類的內容 如果只想看這個可以直接翻到最後/(ㄒoㄒ)/

  • 如何自己得到一個代理類的內容

  • 如何看原始碼

  • 變強

老規矩,上程式碼觀現象

看過Jdk動態代理類長啥樣嗎?Jdk動態代理原理原始碼一本到
依賴的類,明白上面的過程可以跳過不看

public interface HelloService {
    void sayHello();
}
/******************************************************/
public class HelloServiceImpl implements HelloService {
  @Override
  public void sayHello() {
    System.out.println("給掘金大佬們低頭 (:");
  }
}
/****************************************************/
public class HelloInvocationHandle implements InvocationHandler {

    private Object target;

    public HelloInvocationHandle(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy before=======");
        Object result = method.invoke(target, args);
        System.out.println("proxy end=======");
        return result;
    }
}
複製程式碼

上面的程式碼,相信大家在學動態代理的時候,都有寫過,我就不贅述了,大家有想過他是怎麼實現的嗎?為什麼調代理類的sayHello()就可以直接使用InvocationHandler裡的invoke()邏輯呢?為什麼代理類必須要實現一個介面這麼麻煩呢?我們繼續往下面看。

原始碼分析

我們看上面的Main方法,可以看出來Proxy.newProxyInstance()這個方法承載了代理類的所有的邏輯,所有的魔法都在這裡面

//生成一個代理物件
HelloService proxy = (HelloService) Proxy.newProxyInstance(
    Thread.currentThread().getContextClassLoader(),
    hello.getClass().getInterfaces(),handle);

複製程式碼

我們點進去,看看裡面長啥樣

    private static final Class<?>[] constructorParams =
        { InvocationHandler.class };

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
        ...
        //這裡的interface陣列裡面放的是 HelloService,就是代理類需要實現的那個介面
        //這邊複製了一份等待處理
        final Class<?>[] intfs = interfaces.clone();
        ...
        //得到了代理類的Class
        Class<?> cl = getProxyClass0(loader, intfs);
        ...
        //傳入構造物件拿到代理類的構造器
        //從這裡我們可以猜出這個代理類一個有一個構造方法是傳入InvocationHandler進行初始化的
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        //通過反射傳入之前我們定義的那個HelloInvocationHandle,進行構造器例項化代理物件
        return cons.newInstance(new Object[]{h});
複製程式碼

簡單的分析一下上面的程式碼,流程也很簡單

  • 傳入HelloService.class類進行代理類的生成,這樣代理類就有了原始類的方法資訊,例如sayHello()
  • 通過一個InvocationHandler這個構造物件拿到該代理類的構造方法
  • 傳入之前我們定義的HelloInvocationHandle,構造器例項化了活生生的代理物件 從這裡我們大致就能明白了Jdk動態代理的原理:克隆一個介面,生成一個新類作為代理類,這個類裡面有著我們定義的HelloInvocationHandle進行代理邏輯的處理

這裡就回答了我們上面提到的一個問題: Jdk動態代理,代理類必須實現介面,是因為跟他的實現有關係,他規定了必須要傳一個介面去生成代理類

所以所有的謎團就在生成代理類的getProxyClass0(loader, intfs)這個方法裡

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
       ...
        // 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
        //這裡從一個代理快取的Cache中得到代理,Cache裡面涉及虛引用實引用的東西不在我們討論的範圍裡,我們直接看這個類是怎麼生成的
        return proxyClassCache.get(loader, interfaces);
    }

//從上面的註釋和這裡我們都可以看出代理類是從一個叫ProxyClassFactory裡生成的
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
     proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); 
複製程式碼

看上面的註釋我們可以看出到ProxyClassFactory負責生成代理類

我這裡看的是jdk1.8的原始碼,用lambda重構過,舊版本看到的可以不一樣,主邏輯應該沒什麼變化

// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    ...
    //這裡定義了代理類的名字,所以你每次看到的代理類都是$Proxy開頭的
    String proxyName = proxyPkg + proxyClassNamePrefix + num;
    //關鍵點在這裡,這裡生成一個代理類
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    proxyName, interfaces, accessFlags);
}
複製程式碼

ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags)這個方法如果感興趣的大佬點進去可以看到,裡面是用StringBuilder拼出了這個代理類的位元組碼(:,然後轉成了Byte陣列。

Bingo

關鍵點來了! 這個代理類長啥樣怎麼看?Debug沒有暴露出來啊親,看不了,小之看到上面的byte[] proxyClassFile這個變數,漏出了一陣淫笑,前面說過,這個變數裡存的是代理類位元組碼內容啊!沒錯!輸出流伺候! 小之一頓操作,QWER!飛起

看過Jdk動態代理類長啥樣嗎?Jdk動態代理原理原始碼一本到

哦呵

看過Jdk動態代理類長啥樣嗎?Jdk動態代理原理原始碼一本到

反編譯,哦呵

public final class $Proxy0 extends Proxy implements HelloService {
    private static Method m1; //equals()方法
    private static Method m3; //sayHello()方法
    private static Method m2; //Bingo 我們的sayHello方法
    private static Method m0; //hashCode()方法
    
    //這裡就是我們之前提交的InvocationHandler這個構造方法!!
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    //勇士來吧!看一看我們的代理類的sayHello()方法長什麼樣子呀!!
    public final void sayHello() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m3 = Class.forName("proxy.service.HelloService").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]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
複製程式碼

筒子們,請聚焦上面的代理類sayHello()方法。真相大白了吧?代理類裡實現了介面裡的方法,全部呼叫了你的HelloInvocationinvoke()方法啦!

這裡說一下上面的super.h.invoke(this, m3, (Object[])null);super.h是什麼東西,明眼的大佬都應該看出來了吧?繼承了Proxy類,是Proxy裡定義的InvocationHandler變數,也就是你的HelloInvocation

看過Jdk動態代理類長啥樣嗎?Jdk動態代理原理原始碼一本到

總結

大佬們,是不是覺得Jdk動態代理也不是很複雜?好像自己也能寫出來?哈哈,本篇文章還想提供一個Debug程式碼的思路,其實看原始碼沒那麼難,希望可以幫到大家,我們下次再見啦~

對了我這裡安利一下我的Github點我點我 目前正在做的幾個東西:

Triple 自己寫的一個RPC框架,練手的,功能不是很完善,主要目的是為了藉助這個去理解網路相關的知識點,如Netty,想起來寫一點~

資料結構和演算法 資料結構和演算法是薄弱項,多學學練練好吹吹??呀

Spring原始碼分析 還在分析,裡面會整理一些問題,帶著問題去看原始碼效率更高一點,歡迎大家有什麼關於Spring的不懂的,可以給我提Issue,文章寫好了應該也會發到掘金,畢竟基佬多(逃。

相關文章