天天看點

從JDK源碼分析動态代理原理

       大家都知道像Spring AOP、MyBatis等一些優秀的架構源碼裡都在大量使用動态代理,動态代理可以在不改變源碼結構的情況下使得方法功能可以前置增強以及後置增強。當然代理模式也是23種設計模式之一。但是今天不是讨論動态代理怎麼使用,而是動态代理在JDK的底層是怎麼實作的?

我們先看一個簡單動态代理的例子

1、被代理對象的接口

package com.mzt.proxy;

/**
 * @author 馬志濤
 */
public interface ITest {
    public void sayHello();
}
           

 2、被代理對象的接口的實作

package com.mzt.proxy;

/**
 * @author 馬志濤
 */
public class TestImpl implements ITest {
    @Override
    public void sayHello() {
        System.out.println("this is myself");
    }
}
           

3、代理對象輔助類實作InvocationHandler

package com.mzt.proxy;

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

/**
 * @author 馬志濤
 */
public class TestProxy  implements InvocationHandler {
    Object obj;
    public TestProxy(Object obj){
        this.obj = obj;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("前置增強實作...");
        method.invoke(obj , args);
        System.out.println("後置增強實作...");
        return obj;
    }
}
           

 4、測試用戶端

package com.mzt.proxy;

import java.lang.reflect.Proxy;

/**
 * @author 馬志濤
 */
public class MainTest {
    public static void main(String[] args) {
        ITest iTest = new TestImpl();
        TestProxy testProxy = new TestProxy(iTest);
        ITest test = (ITest)Proxy.newProxyInstance(ITest.class.getClassLoader(),new Class[]{ITest.class} , testProxy);
        test.sayHello();
    }
}
           

5、運作程式結果

從JDK源碼分析動态代理原理

6、從第5步可以看到在沒有改變sayHello() 方法裡代碼的情況下,實作了前置增強和後置增強的實作,調試代碼看代理對象test是$Proxy0的形式

從JDK源碼分析動态代理原理

思考:

我們知道一個類的完整生命周期是以下幾個步驟:

Java源檔案(Java檔案) ----> Java位元組碼檔案(.class檔案) ----> Class對象 ----> 執行個體對象----- >解除安裝

那麼問題來了

它是怎麼樣在記憶體中生成?

生成的class的檔案結構是什麼樣的?

1、動态代理類是事先不存在Java源檔案和Java位元組碼的,那麼它是怎麼樣跳過這兩步生成Class對象的呢?我們進入Proxy.newProxyInstance()源碼一探究竟。

從JDK源碼分析動态代理原理

2、進入getProxyClass0這個方法

從JDK源碼分析動态代理原理

 3、進入proxyClassCache.get(loader,interfaces)方法

從JDK源碼分析動态代理原理

 4、進入subKeyFactory.apply(key,parameter)方法,是個接口

從JDK源碼分析動态代理原理

5、進入ProxyClassFactory這個實作,可以看出代理類為什麼是$Proxy0開頭的,繼續往下看

從JDK源碼分析動态代理原理
從JDK源碼分析動态代理原理

 6、關鍵的一句代碼,看注釋Generate the specified proxy class,可以看出來是在這裡生成位元組碼檔案的

從JDK源碼分析動态代理原理

7、我們想要檢視proxyClassFile這個位元組碼檔案的内容就必須要将它導出一個class檔案到磁盤,然後反編譯可以檢視其内容。以下代碼是工具類,入參是代理工具類的全限名(proxyName)和被代理對象的接口(interfaces)

package com.mzt.proxy;

import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 将動态代理位元組碼檔案輸出到磁盤
 * 馬志濤
 */
public class ClassOutUtil {
    public void outPutFile(String proxyName, Class interfaces){
        String paths = interfaces.getResource(".").getPath() + proxyName + ".class";
        /*
         * Generate the specified proxy class.
         */
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, new Class[]{interfaces});

        FileOutputStream fileOutputStream = null;
        try{
            fileOutputStream = new FileOutputStream(paths);
            fileOutputStream.write(proxyClassFile);
            fileOutputStream.flush();
        }catch (Exception ex){
            ex.printStackTrace();
        }finally {
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ClassOutUtil classOutUtil = new ClassOutUtil();
        classOutUtil.outPutFile("com.mzt.TestProxy", ITest.class);
    }
}
           

 8、我們可以很神奇的看到生成了一個com.mzt.TestProxy.class檔案,生成的位元組碼檔案内容如下

從JDK源碼分析動态代理原理
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.mzt;

import com.mzt.proxy.ITest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class TestProxy extends Proxy implements ITest {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public TestProxy(InvocationHandler var1) throws  {
        super(var1);
    }

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

    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);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.mzt.proxy.ITest").getMethod("sayHello");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
           

 9、代理類繼承了Proxy類,實作了ITest接口,我們看一下sayHello()方法,原來核心是h.invoke()這個方法

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);
    }
}
           

10、代理類中并沒有h這個對象,我們檢視它的父類Proxy,原來h是父類中的InvocationHandler,也就是說利用代理類調用方法的時候,實際上是調用了輔助類中的Invoke方法,到此為止動态代理原理基本上搞懂了。

從JDK源碼分析動态代理原理