java動態代理——代理方法的假設和驗證及Proxy原始碼分析五

tera發表於2020-08-05

前文地址

https://www.cnblogs.com/tera/p/13419025.html

本系列文章主要是博主在學習spring aop的過程中瞭解到其使用了java動態代理,本著究根問底的態度,於是對java動態代理的本質原理做了一些研究,於是便有了這個系列的文章

這個系列的文章的初衷是為了研究java動態代理的原理,因此最重要的一部分就是代理方法究竟是如何被定義的

此時我們先停一停,思考這樣一個問題:
如果由我們自己通過程式碼來定義一個Proxy的動態類,我們該如何去定義?
首先回顧一下第一篇文章中提到代理類的3個特性
1.繼承了Proxy類
2.實現了我們傳入的介面
3.以$Proxy+隨機數字的命名
假定我們現在定義一個簡單的介面,並生成該介面的代理類
介面定義

public interface TestInterface {
    int put(String a);
}

滿足3個特性的代理類初步定義如下

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

public class $Proxy11 extends Proxy implements TestInterface {
    protected $Proxy11(InvocationHandler h) {
        super(h);
    }

    @Override
    public int put(String a) {
        return 0;
    }
}

然而在這種情況下h的代理是無法生效的,因為put方法中並沒有h的參與
現在我們回顧一下InvocationHandler的invoke方法的定義

public Object invoke(Object proxy, Method method, Object[] args)

第一個proxy是代理自身,method是被代理的方法,args是方法的引數
因此為了使得代理生效,我們可以修改方法,如下

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

public class $Proxy11 extends Proxy implements TestInterface {
    protected $Proxy11(InvocationHandler h) {
        super(h);
    }

    @Override
    public int put(String a) {
        try {
            return (int) h.invoke(this, TestInterface.class.getMethod("put", String.class), new Object[]{a});
        } catch (Throwable e) {
            return 0;
        }
    }
}

這樣我們就能使得h的代理生效了
當然,這只是我們所設想的最基本的一種代理形式。有了這個思路之後,我們就可以看看原始碼中是如何生成方法的位元組碼

接著我們來看重點,proxy方法的寫入
還是回到generateClassFile()方法中關注下面這行程式碼

this.methods.add(var16.generateMethod());

這個方法就是proxy方法實際執行的code部分了,因為程式碼比較多,所以我就直接將註釋寫到程式碼中

如果你前面4篇文章都沒落下,那我想你一定會有興趣看完下面所有的程式碼,並且會對proxy的實現和class位元組碼有更深刻的理解

當然,如果你看到原始碼就非常頭疼也沒有關係,可以跳過這部分原始碼直接看最後的驗證部分

private ProxyGenerator.MethodInfo generateMethod() throws IOException {
    /**
     * 獲取方法描述,如果還開啟著之前javap的工具的話,就能看到類似於
     * // java/lang/Object."<init>":()V
     * // Test.calc:(II)I
     */
    String methodDescriptor = ProxyGenerator.getMethodDescriptor(this.parameterTypes, this.returnType);
    /**
     * 這裡和之前構造器一樣,先生成一個MethodInfo物件
     * 這裡17表示public final
     * Modifier.FINAL | Modifier.PUBLIC
     */
    ProxyGenerator.MethodInfo methodInfo = ProxyGenerator.this.new MethodInfo(this.methodName, methodDescriptor, 17);
    /**
     * 新建一個存放靜態池編號的陣列
     */
    int[] parameterTypesOrders = new int[this.parameterTypes.length];
    /**
     * 這個值是指靜態池中的編號,如果還開啟著之前javap的話,類似於
     * Constant pool:
     *    #1 = Methodref          #8.#19         // java/lang/Object."<init>":()V
     *    #2 = Methodref          #7.#20         // Test.calc:(II)I
     *    #3 = Double             2.0d
     *    #5 = Methodref          #21.#22        // java/lang/Math.pow:(DD)D
     * 前面的#1,#2,#3,#5
     * 我們注意到缺少了#4,因為double需要佔用8個位元組,而其他的都只需要佔用4個位元組
     */
    int constantPoolNumber = 1;

    for(int i = 0; i < parameterTypesOrders.length; ++i) {
        parameterTypesOrders[i] = constantPoolNumber;
        /**
         * 如果是Long或者Double型別的引數,則+2,否則+1,因為Long和Double都是佔用8個位元組
         */
        constantPoolNumber += ProxyGenerator.getWordsPerType(this.parameterTypes[i]);
    }

    DataOutputStream dataOutputStream = new DataOutputStream(methodInfo.code);
    /**
     * aload_0,載入棧幀本地變數表的第一個引數,因為是例項方法,所以是就是指this
     */
    ProxyGenerator.this.code_aload(0, dataOutputStream);
    /**
     * getfield,獲取this的例項欄位
     */
    dataOutputStream.writeByte(180);
    /**
     * 從Proxy類中,獲取型別是InvocationHandler,欄位名為h的物件
     */
    dataOutputStream.writeShort(ProxyGenerator.this.cp.getFieldRef("java/lang/reflect/Proxy", "h", "Ljava/lang/reflect/InvocationHandler;"));
    /**
     * aload_0
     */
    ProxyGenerator.this.code_aload(0, dataOutputStream);
    /**
     * getstatic,獲取靜態欄位
     */
    dataOutputStream.writeByte(178);
    /**
     * 獲取當前代理類,名字是methodFieldName,型別是Method的物件(之前在寫入靜態池的時候,用的也是methodFieldName)
     */
    dataOutputStream.writeShort(ProxyGenerator.this.cp.getFieldRef(ProxyGenerator.dotToSlash(ProxyGenerator.this.className), this.methodFieldName, "Ljava/lang/reflect/Method;"));
    /**
     * 準備寫入引數
     */
    if (this.parameterTypes.length > 0) {
        /**
         * 寫入引數的數量,如果再仔細看一下code_ipush
         * 當length小於等於5時,寫入的命令是iconst_m1~iconst_5
         * 當length在-128~127閉區間時,寫入的命令是bipush
         * 否則就寫入sipush
         */
        ProxyGenerator.this.code_ipush(this.parameterTypes.length, dataOutputStream);
        /**
         * anewarray,建立一個陣列
         */
        dataOutputStream.writeByte(189);
        /**
         * 陣列的型別是object
         */
        dataOutputStream.writeShort(ProxyGenerator.this.cp.getClass("java/lang/Object"));

        /**
         * 迴圈引數
         */
        for(int i = 0; i < this.parameterTypes.length; ++i) {
            /**
             * dup,複製棧頂的運算元
             */
            dataOutputStream.writeByte(89);
            /**
             * iconst、bipush、sipush
             */
            ProxyGenerator.this.code_ipush(i, dataOutputStream);
            /**
             * 對引數型別等做一個編碼
             */
            this.codeWrapArgument(this.parameterTypes[i], parameterTypesOrders[i], dataOutputStream);
            /**
             * aastore,將物件存入陣列
             */
            dataOutputStream.writeByte(83);
        }
    } else {
        /**
         * 如果沒引數的話
         * aconst_null,push一個null
         */
        dataOutputStream.writeByte(1);
    }
    /**
     * invokeinterface 呼叫介面方法
     */
    dataOutputStream.writeByte(185);
    /**
     * 找到InvocationHandler的invoke方法
     */
    dataOutputStream.writeShort(ProxyGenerator.this.cp.getInterfaceMethodRef("java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;"));
    /**
     * iconst_1,將1壓入操作棧
     */
    dataOutputStream.writeByte(4);
    /**
     * nop,不做事情
     */
    dataOutputStream.writeByte(0);

    if (this.returnType == Void.TYPE) {
        /**
         * 如果是void方法
         * pop,將棧頂的運算元彈出
         */
        dataOutputStream.writeByte(87);
        /**
         * return
         */
        dataOutputStream.writeByte(177);
    } else {
        /**
         * 對返回值進行編碼
         */
        this.codeUnwrapReturnValue(this.returnType, dataOutputStream);
    }

    byte startPc = 0;
    short handlerPc;
    short endPc = handlerPc = (short)methodInfo.code.size();
    /**
     * 獲取方法可能丟擲的異常
     */
    List catchList = ProxyGenerator.computeUniqueCatchList(this.exceptionTypes);
    if (catchList.size() > 0) {
        Iterator exceptionIterator = catchList.iterator();

        /**
         * 對異常進行預處理
         */
        while(exceptionIterator.hasNext()) {
            Class var12 = (Class)exceptionIterator.next();
            /**
             * 這裡注意startPc, endPc, handlerPc引數,和pc register有關,用於丟擲Exception時能確定接下去要執行的指令
             */
            methodInfo.exceptionTable.add(new ProxyGenerator.ExceptionTableEntry(startPc, endPc, handlerPc, ProxyGenerator.this.cp.getClass(ProxyGenerator.dotToSlash(var12.getName()))));
        }
        /**
         * athrow,丟擲異常
         */
        dataOutputStream.writeByte(191);
        /**
         * 重新獲取異常的處理點
         */
        handlerPc = (short)methodInfo.code.size();
        /**
         * 新增異常的基類
         */
        dataOutputStream.exceptionTable.add(new ProxyGenerator.ExceptionTableEntry(startPc, endPc, handlerPc, ProxyGenerator.this.cp.getClass("java/lang/Throwable")));
        /**
         * 根據constantPoolNumber的值
         * astore_0 = 75 (0x4b)
         * astore_1 = 76 (0x4c)
         * astore_2 = 77 (0x4d)
         * astore_3 = 78 (0x4e)
         * astore
         */
        ProxyGenerator.this.code_astore(constantPoolNumber, dataOutputStream);
        /**
         * new 建立一個新物件
         */
        dataOutputStream.writeByte(187);
        /**
         * 物件是UndeclaredThrowableException
         */
        dataOutputStream.writeShort(ProxyGenerator.this.cp.getClass("java/lang/reflect/UndeclaredThrowableException"));
        /**
         * dup 複製棧頂運算元
         */
        dataOutputStream.writeByte(89);
        /**
         * 根據constantPoolNumber的值
         * aload_0 = 42 (0x2a)
         * aload_1 = 43 (0x2b)
         * aload_2 = 44 (0x2c)
         * aload_3 = 45 (0x2d)
         * aload
         */
        ProxyGenerator.this.code_aload(constantPoolNumber, dataOutputStream);
        /**
         * invokespecial,呼叫父類的方法
         */
        dataOutputStream.writeByte(183);
        /**
         * 父類的建構函式
         */
        dataOutputStream.writeShort(ProxyGenerator.this.cp.getMethodRef("java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V"));
        /**
         * athrow,丟擲異常
         */
        dataOutputStream.writeByte(191);
    }

    if (var2.code.size() > 65535) {
        throw new IllegalArgumentException("code size limit exceeded");
    } else {
        var2.maxStack = 10;
        var2.maxLocals = (short)(var4 + 1);
        var2.declaredExceptions = new short[this.exceptionTypes.length];

        for(int var14 = 0; var14 < this.exceptionTypes.length; ++var14) {
            var2.declaredExceptions[var14] = ProxyGenerator.this.cp.getClass(ProxyGenerator.dotToSlash(this.exceptionTypes[var14].getName()));
        }

        return var2;
    }
}

 

那麼為了看看我們一開始對於方法的猜測是否正確,我們略微改造之前定義的介面和類,然後實際看看
介面和Proxy定義

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.concurrent.TimeoutException;

public class Proxy11 extends Proxy implements TestInterface {
    protected Proxy11(InvocationHandler h) {
        super(h);
    }

    public void put(String a, Double b) throws TimeoutException {
        try {
            h.invoke(this, TestInterface.class.getMethod("put", String.class, Double.class), new Object[]{a, b});
        } catch (Throwable e) {
        }
    }

    public int get(String a, Long b) throws IndexOutOfBoundsException {
        try {
            return (int) h.invoke(this, TestInterface.class.getMethod("get", String.class, Long.class), new Object[]{a, b});
        } catch (Throwable e) {
            return 0;
        }
    }
}


interface TestInterface {
    void put(String a, Double b) throws TimeoutException;

    int get(String a, Long b) throws IndexOutOfBoundsException;
}

我們生成class後,將位元組碼的指令集與我們之前的分析一一對比,雖然其中還是有些不同,不過大體上是符合之前原始碼的順序

 

最後為了實際考察Proxy生成類的原始碼,我們還是需要將Proxy的位元組碼轉換回java檔案

首先我們需要新增vm啟動引數

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

有了這個引數,當我們使用Proxy時,就會把class寫入到檔案中了
寫入的目錄是專案下的com/sun/proxy/$Proxy11.class
為了更好地可讀性,我們需要使用一個線上工具
http://www.javadecompilers.com/
傳入我們之前生成出來class檔案
結果如下

package com.sun.proxy;

import java.util.concurrent.TimeoutException;
import java.lang.reflect.UndeclaredThrowableException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import cn.tera.aopproxy.TestInterface;
import java.lang.reflect.Proxy;

public final class $Proxy11 extends Proxy implements TestInterface
{
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;
    
    public $Proxy11(final InvocationHandler h) {
        super(h);
    }
    
    public final boolean equals(final Object o) {
        try {
            return (boolean)super.h.invoke(this, $Proxy11.m1, new Object[] { o });
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable undeclaredThrowable) {
            throw new UndeclaredThrowableException(undeclaredThrowable);
        }
    }
    
    public final int get(final String s, final Long n) throws IndexOutOfBoundsException {
        try {
            return (int)super.h.invoke(this, $Proxy11.m3, new Object[] { s, n });
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable undeclaredThrowable) {
            throw new UndeclaredThrowableException(undeclaredThrowable);
        }
    }
    
    public final String toString() {
        try {
            return (String)super.h.invoke(this, $Proxy11.m2, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable undeclaredThrowable) {
            throw new UndeclaredThrowableException(undeclaredThrowable);
        }
    }
    
    public final void put(final String s, final Double n) throws TimeoutException {
        try {
            super.h.invoke(this, $Proxy11.m4, new Object[] { s, n });
        }
        catch (Error | RuntimeException | TimeoutException error) {
            throw;
        }
        catch (Throwable undeclaredThrowable) {
            throw new UndeclaredThrowableException(undeclaredThrowable);
        }
    }
    
    public final int hashCode() {
        try {
            return (int)super.h.invoke(this, $Proxy11.m0, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable undeclaredThrowable) {
            throw new UndeclaredThrowableException(undeclaredThrowable);
        }
    }
    
    static {
        try {
            $Proxy11.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            $Proxy11.m3 = Class.forName("cn.tera.aopproxy.TestInterface").getMethod("get", Class.forName("java.lang.String"), Class.forName("java.lang.Long"));
            $Proxy11.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class<?>[])new Class[0]);
            $Proxy11.m4 = Class.forName("cn.tera.aopproxy.TestInterface").getMethod("put", Class.forName("java.lang.String"), Class.forName("java.lang.Double"));
            $Proxy11.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class<?>[])new Class[0]);
        }
        catch (NoSuchMethodException ex) {
            throw new NoSuchMethodError(ex.getMessage());
        }
        catch (ClassNotFoundException ex2) {
            throw new NoClassDefFoundError(ex2.getMessage());
        }
    }
}

是不是有一種恍然大悟的感覺,此時再回頭去看之前分析的方法位元組碼,就能更好地理解其含義了,以及和我們自己定義的類的位元組碼有區別的原因了。

當然我們更可以直接檢視生成的class檔案,再通過javap去檢視位元組碼,然後返過去和前面的原始碼再作對比,這個就留給讀者自己去分析了

 

至此,java動態代理的根本原理和相應的class位元組碼結構的分析到此就結束了

 

相關文章