JDK 動態代理的原理

寫在前面

未經允許不得轉載!

  • 什麼是代理?
    • (我的理解)在編程語言中,代理就是由於某些原因控制某個對象/方法的訪問
  • 爲什麼要有靜態代理?
    • 開閉原則,需求總是變化無常的,在不修改原有邏輯的情況下,對其進行拓展
  • 爲什麼要有動態代理?
    • 用靜態代理的原因,動態代理也有
    • 方便維護,從靜態代理的維護實現類到現在只需要維護拓展邏輯

這篇文章有好多隱藏的知識點,用心看用心挖掘,最好自己寫一遍代碼,走一遍完整的邏輯。

靜態代理

靜態代理感覺沒啥好說的,實現方式可以是組合,也可以是繼承,當然是推薦用組合的方式實現靜態代理,理由是解耦,可以任意組裝需要的組合,比如說你要對原邏輯代理3次(我的案例可以拆成兩個代理,一個校驗參數,一個打印信息),進行不同的業務邏輯,且執行的順序可能會變化。

代碼看附錄,需要用到的類: Calculable, Calculator, CalculatorStaticProxy, CalculatorStaticProxyTest

動態代理

動態代理相對於靜態代理來說,目標是一致的,就是控制目標方法的訪問,但是代理類是動態生成的。
重點來了:動態生成代理類,生成方式也有好多種,比如說:生成新的一個代理類,將原類改造成代理類。

JDK 動態代理

(這裏只說果,瞭解因看下面的原理解析),還有排除java.lang.Object(本來就是所有實現類的 super class)

這段很重要,結論:
代理類一定是java.lang.reflect.Proxy的子類(所以 JDK 動態代理只能代理有interface的類及其方法),並且implements了指定類的所有interface,並且一定有且只有一個帶java.lang.reflect.InvocationHandler參數的Constructor方法,實例化對象的時候一定會將 InvocationHandler 帶入。

還有個很重要的點: 爲什麼創建代理對象的時候,要指定 ClassLoder?
我覺得有兩個原因:
* 可以實現安全機制,註釋上有說明
* 跟隨目標類進行同步熱加載類

然後,當你調用某個interface的方法時,最終會轉發給上面構造器帶入的那個對象的invoke方法,搞定,收工。

代碼看附錄,需要用到的類: Calculable, Calculator, CalculatorJdkDynamicProxyHandler, CalculatorJdkDynamicProxyTest, $CalculatorJdkDynamicProxy(這個類就是生成的代理類)。
基於JDK 8u201

原理解析

想要動態生成對象的前提是:要有這個對象的 Class 對象。
想要有這個對象的 Class 對象的前提是:要有這個對象的 class (也就是字節碼),然後通過 ClassLoader 加載到方法區的。
所以問題就在怎麼動態生成字節碼,並且怎麼加載到方法區的。

那麼來看看JDK 動態代理怎麼實現的

解析基於JDK 8u201
先來看看代理類的結構:

  • 第1部分:定義 class 的那段
  • 第2部分:定義 fields 的那段
  • 第3部分:定義 constructor methods 的那段
  • 第4部分:定義 methods 的那段

然後來看看代理類的字節碼是怎麼生成的(只說關鍵部分):

  • 調用順序:
    • java.lang.reflect.Proxy#newProxyInstance
    • java.lang.reflect.Proxy#getProxyClass0
    • proxyClassCache.get
    • private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
    • java.lang.reflect.WeakCache#get
    • subKeyFactory.apply(key, parameter)(不重要)
    • java.lang.reflect.Proxy.KeyFactory#apply(不重要)
    • factory = new Factory(key, parameter, subKey, valuesMap);
    • supplier = factory;
    • supplier.get()
    • java.lang.reflect.WeakCache.Factory#get
    • valueFactory.apply
    • java.lang.reflect.Proxy.ProxyClassFactory#apply
      走到這裏開始最重要的那塊了 !!!
  • class name 怎麼生成的,怎麼拿到全部的 interface自己看
  • 先來看下怎麼加載動態生成的字節碼的
    • return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
    • 不是通過雙親委派的形式加載的,但是這個動態生成的類加載器是AppClassLoader
  • 再來看怎麼生成字節碼的sun.misc.ProxyGenerator#generateProxyClass(java.lang.String, java.lang.Class<?>[], int)
  • 如果要保存生成的代理類,查看saveGeneratedFiles這個字段
  • 再來看生成字節碼的邏輯
    • sun.misc.ProxyGenerator#generateClassFile
    • ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
    • 進入sun.misc.ProxyGenerator#generateClassFile方法後,第一次看建議反着看,從結果往前推進
    • 從結果那可以看出,這字節碼是 jdk 自己生成的
    • 然後跟着上面的代理類的結構一步步看上去
    • 解密上面的一些結論:
      • 有且只有一個帶java.lang.reflect.InvocationHandler參數的Constructor方法
        • this.methods.add(this.generateConstructor());
      • 爲什麼父類是Proxy
        • var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
    • 下面附上了 ClassFile Structure,助你一臂之力,是不是豁然開朗了 ^^
    • var14.writeInt(-889275714);對應的是 magic: cafebabe
    • 一步步對過來
ClassFile {
  u4 magic;
  u2 minor_version;
  u2 major_version;
  u2 constant_pool_count;
  cp_info constant_pool[constant_pool_count-1];
  u2 access_flags;
  u2 this_class;
  u2 super_class;
  u2 interfaces_count;
  u2 interfaces[interfaces_count];
  u2 fields_count;
  field_info fields[fields_count];
  u2 methods_count;
  method_info methods[methods_count];
  u2 attributes_count;
  attribute_info attributes[attributes_count];
}

知識補充

  • 除了 ClassLoder ,還可以用sun.misc.Unsafe#defineClass來把加載類信息

附錄

public interface Calculable {
    Integer divide(int a, int b);
}
public class Calculator implements Calculable {
    @Override
    public Integer divide(int a, int b) {
        return a / b;
    }
}
public class CalculatorStaticProxy implements Calculable {
    private Calculable target;

    public CalculatorStaticProxy(Calculable target) {
        this.target = target;
    }

    @Override
    public Integer divide(int a, int b) {
        Integer result = null;

        boolean isPass = beforeDivideProxy(a, b);
        if (isPass) {
            result = target.divide(a, b);

            afterDivideProxy(a, b);
        }

        return result;
    }

    private boolean beforeDivideProxy(int a, int b) {
        System.out.printf("before exec %d / %d\n", a, b);
        if (b == 0) {
            return false;
        }
        return true;
    }

    private void afterDivideProxy(int a, int b) {
        System.out.printf("after divide method\n");
    }
}
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

public class CalculatorStaticProxyTest {
    @Test
    public void testDivide_normal() {
        Calculable calc = new CalculatorStaticProxy(new Calculator());
        assertEquals(Integer.valueOf(2), calc.divide(6, 3));
    }

    @Test
    public void testDivide_exception() {
        Calculable calc = new CalculatorStaticProxy(new Calculator());
        assertNull(calc.divide(6, 0));
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class CalculatorJdkDynamicProxyHandler implements InvocationHandler {
    private Calculable target;

    public CalculatorJdkDynamicProxyHandler(Calculable target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if ("divide".equals(methodName)) {
            return handleDivideMethod(method, args);
        }
        return method.invoke(target, args);
    }

    private Integer handleDivideMethod(Method method, Object[] args)
            throws InvocationTargetException, IllegalAccessException
    {
        Integer result = null;

        boolean isPass = beforeDivideProxy(method, args);
        if (isPass) {
            result = (Integer) method.invoke(target, args);
            afterDivideProxy(method, args);
        }

        return result;
    }

    private boolean beforeDivideProxy(Method method, Object[] args) {
        System.out.printf("before exec divide method\n", args[0], args[1]);
        if ((Integer) args[1] == 0) {
            return false;
        }
        return true;
    }

    private void afterDivideProxy(Method method, Object[] args) {
        System.out.printf("after divide method\n");
    }
}

import org.junit.Before;
import org.junit.Test;

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

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

public class CalculatorJdkDynamicProxyTest {
    private Calculable proxyCalc = null;

//    public static void main(String[] args) {
//        // 保存生成的代理類的字節碼文件: jdk8 下的配置,默認生成的路徑是項目根目錄
//        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//
//        sun.misc.ProxyGenerator.generateProxyClass(
//                "$CalculatorJdkDynamicProxy",
//                Calculator.class.getInterfaces(),
//                java.lang.reflect.Modifier.PUBLIC);
//    }

    @Before
    public void beforeTest() {
        Calculable calc = new Calculator();
        ClassLoader loader = calc.getClass().getClassLoader();
        Class<?>[] interfaces = calc.getClass().getInterfaces();
        InvocationHandler h = new CalculatorJdkDynamicProxyHandler(calc);

        proxyCalc = (Calculable) Proxy.newProxyInstance(loader, interfaces, h);
        System.out.println(proxyCalc.getClass().getName());
        System.out.println(proxyCalc.getClass().getClassLoader());
    }

    @Test
    public void testDivide_normal() {
        assertEquals(Integer.valueOf(2), proxyCalc.divide(6, 3));
    }

    @Test
    public void testDivide_exception() {
        Calculable calc = new CalculatorStaticProxy(new Calculator());
        assertNull(calc.divide(6, 0));
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public class $CalculatorJdkDynamicProxy extends Proxy implements Calculable {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $CalculatorJdkDynamicProxy(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 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 Integer divide(int var1, int var2) throws  {
        try {
            return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("Calculable").getMethod("divide", Integer.TYPE, Integer.TYPE);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章