JDK 動態代理源碼解析

Java 提供給了我們一個非常方便的動態代理類 Proxy,讓我們今天來研究一下它的實現原理,以及爲什麼動態代理會存在性能問題。

個人博客:https://blog.N0tExpectErr0r.cn
小專欄:https://xiaozhuanlan.com/N0tExpectErr0r

代理對象的創建

我們往往通過 newProxyInstance(ClassLoader, Class<?>[], InvocationHandler) 方法進行代理對象的創建,它傳遞了三個參數:loaderinterfaces 以及 h

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h) 
                                      throws IllegalArgumentException {
    Objects.requireNonNull(h);
    final Class<?>[] intfs = interfaces.clone();
    // 通過 SercurityManager 進行對即將代理的類的 PackageAccess 的權限檢測
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }
    // 嘗試獲取或創建代理類
    Class<?> cl = getProxyClass0(loader, intfs);
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        // 對非public類通過AccessController將Accessible設置爲true
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        // 以 InvocationHandler 作爲參數調用代理類的構造函數
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

這裏主要是如下的幾步:

  1. 通過 SecurityManager 對即將代理的類的 package access 進行檢測。
  2. 通過 getProxyClass0 嘗試獲取代理 Class 類。
  3. 對非 public 的類,通過 AccessControllerAccessible 設置爲 true
  4. InvokeHandler 作爲參數調用代理類的構造函數構造對象。

顯然,動態代理的關鍵想必就是在 getProxyClass0 這個方法中了,可以大膽猜測一下這個代理類中的任何方法的調用都會通過傳遞進去的 InvokeHandler 進行代理。

我們看到 getProxyClass0

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    // 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
    return proxyClassCache.get(loader, interfaces);
}

很奇怪,這裏竟然只有一個從 proxyClassCache.get 的調用來獲取緩存中的代理 Class,剛開始看到這裏的時候以爲 proxyClassCache 是個 Map 導致非常困惑。之後看到了上面的註釋:

如果代理類已經被給定的 ClassLoader 實現過了,則從緩存中直接 copy 一份拿出,否則它會通過代理類的工廠 ProxyClassFactory 進行創建

然後再仔細一看,原來 proxyClassCache 是一個 WeakCache 對象😂,那看來對代理 Class 對象的創建就在它的 get 方法中實現了。

WeakCache 緩存

數據結構

首先我們來了解一下 WeakCache 究竟是用來幹什麼的,我們先看到它內部的數據結構:

final class WeakCache<K, P, V> {
    private final ReferenceQueue<K> refQueue
        = new ReferenceQueue<>();
    // the key type is Object for supporting null key
    private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
        = new ConcurrentHashMap<>();
    private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
        = new ConcurrentHashMap<>();
    private final BiFunction<K, P, ?> subKeyFactory;
    private final BiFunction<K, P, V> valueFactory;
}

可以看到,這裏有如下的幾個成員變量:

  • refQueue:引用隊列。
  • map:一個 ConcurrentHashMap,裏面的 value 也是 ConcurrentHashMap,它們的 key 均爲 Object,用來支持爲 null 的 key,而 value 則是 Supplier 類。
  • reverseMap:一個 keySuppliervalueBooleanConcurrentHashMap
  • subKeyFactory:用於生成 subKey 的工廠,也就是 map 中的 Map 的 key
  • valueFactory:用於生成 value 對象的工廠,既然我們的 Classvalue,這裏想必就是 ProxyClassFactory 了。

果然,看到 proxyClassCache 的聲明處:

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
    proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

可以看到,它的 subKeyFactoryKeyFactory 類,而 valueFactoryProxyClassFactory

get

我們接着看看它的 get 方法究竟做了什麼:

public V get(K key, P parameter) {
    Objects.requireNonNull(parameter);
    
    expungeStaleEntries();
    // 通過 CacheKey.valueOf 將 key 轉換爲了存在緩存中的 CacheKey
    Object cacheKey = CacheKey.valueOf(key, refQueue);
    
    // 對 map 中的 value Map 進行獲取,採用了懶創建的思路,這裏還對併發問題進行了考慮
    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
    if (valuesMap == null) {
        ConcurrentMap<Object, Supplier<V>> oldValuesMap
            = map.putIfAbsent(cacheKey,
                              valuesMap = new ConcurrentHashMap<>());
        if (oldValuesMap != null) {
            valuesMap = oldValuesMap;
        }
    }
    // 通過 subKeyFactory 創建 subKey 並獲取對應的 Supplier
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    Supplier<V> supplier = valuesMap.get(subKey);
    Factory factory = null;
    while (true) {
    		// 通過 supplier.get 獲取對應的 value
        if (supplier != null) {
            // supplier might be a Factory or a CacheValue<V> instance
            V value = supplier.get();
            if (value != null) {
                return value;
            }
        }
        // 如果緩存中沒有對應的 supplier,可能是被 CacheValue 清理了,或者 Factory 還未 install
        if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }
        if (supplier == null) {
        		// 將 factory 放入 valuesMap 中,並將其設置爲 supplier
            supplier = valuesMap.putIfAbsent(subKey, factory);
            if (supplier == null) {
                // successfully installed Factory
                supplier = factory;
            }
            // 如果此時已經有 supplier,則用它繼續重試
        } else {
            if (valuesMap.replace(subKey, supplier, factory)) {
                // Factory 成功 install,則將其設置爲 supplier 並重試
                supplier = factory;
            } else {
                // retry with current supplier
                supplier = valuesMap.get(subKey);
            }
        }
    }
}

這裏看上去代碼比較長,其實結構是比較清晰的:

  1. 首先通過 CacheKeykey 轉變爲在 map 中的 key,這裏的 key 實際上是我們的 ClassLoader,也就是這裏先用 ClassLoader 進行了一遍篩選,是第一級緩存。
  2. map 中的 valuesMap 進行獲取,若還沒有則對其進行創建,這裏生成的就是我們的第二級緩存 Map。
  3. 通過 subKeyFactory 創建 subKey 並獲取對應的 Supplier,通過自定義的 subKeyFactory 進行創建,這個 subKey 用於通過第二級緩存獲取真正的數據。
  4. 不斷循環,對 Supplier 進行創建並調用其 get 方法獲取需要的 value。這裏創建 Supplier 的具體邏輯感興趣的可以看看上面的代碼及註釋,最終創造出來的 Supplier 是一個 Factory 對象。

上面的代碼中可以發現考慮到了非常多的併發安全性問題,用到了很多 CAS 操作進行值的 put,值得我們學習。

並且可以發現,WeakCache 中通過以 ClassLoaderkey 的第一級緩存,以及以自定義的 subKeyFactory 生成的 subKeykey 的第二級緩存,極大地加快了 Map 的查找效率,也是很值得我們學習的一個操作。

那麼既然存在 valuesMap 中的 SupplierFactory 對象,讓我們看看 Factory.get 方法:

@Override
public synchronized V get() { // serialize access
    // 再次檢查當前的 Supplier 是不是自己
    Supplier<V> supplier = valuesMap.get(subKey);
    if (supplier != this) {
  
        return null;
    }
    // 通過 valueFactory.apply 方法創建需要的對象
    V value = null;
    try {
        value = Objects.requireNonNull(valueFactory.apply(key, parameter));
    } finally {
        if (value == null) { // remove us on failure
            valuesMap.remove(subKey, this);
        }
    }
    // ... 省略
    return value;
}

可以看到,它實際上就是通過 valueFactory 進行了 value 的創建,並將其返回。

那麼我們對 WeakCache 類進行一下總結:

  • WeakCache 中只有 get 方法,當緩存中沒有存在對應的 value 時,它會通過創建時傳入的 valueFactoryvalue 進行創建。

  • 它通過以 ClassLoaderkey 的第一級緩存以及以自定義的 subKeyFactory 生成的 subKeykey 的第二級緩存,這樣的二級緩存機制極大的加快了我們對 Map 的查找效率。

  • WeakCache 並沒有通過加鎖來保證線程安全,而是將它的線程安全交給了 ConcurrentHashMap 來保證,並且通過大量的 CAS 操作來保證對 Map 操作的安全性。

代理 Class 的創建

ProxyClassFactory

接着我們看看 ProxyClassFactory 是如何生成我們的代理類 Class 對象的:

// 代理類名的前綴
private static final String proxyClassNamePrefix = "$Proxy";
// 下一個用來生成代理類名的數
private static final AtomicLong nextUniqueNumber = new AtomicLong();

@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
    for (Class<?> intf : interfaces) {
        // 遍歷接口列表通過 Class.forName 加載 Class 對象
        Class<?> interfaceClass = null;
        try {
            interfaceClass = Class.forName(intf.getName(), false, loader);
        } catch (ClassNotFoundException e) {
        }
       	// ... 異常檢查
    }
    String proxyPkg = null;     // package to define proxy class in
    int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
    // 如果存在非 public 的接口,記錄非 public 接口的包名,後面會將它們生成的代理類採用同樣的 package access(這些接口必須來自同一個包)
    for (Class<?> intf : interfaces) {
        int flags = intf.getModifiers();
        if (!Modifier.isPublic(flags)) {
        		// 構造
            accessFlags = Modifier.FINAL;
            String name = intf.getName();
            int n = name.lastIndexOf('.');
            String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
            if (proxyPkg == null) {
                proxyPkg = pkg;
            } else if (!pkg.equals(proxyPkg)) {
                throw new IllegalArgumentException(
                    "non-public interfaces from different packages");
            }
        }
    }
    // 沒有非 public 的接口,用 sun.proxy 作爲其包名
    if (proxyPkg == null) {
        proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
    }
    // 構造代理類的名字
    long num = nextUniqueNumber.getAndIncrement();
    String proxyName = proxyPkg + proxyClassNamePrefix + num;
    // 通過 ProxyGenerator.generateProxyClass 生成代理類 Class
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
        proxyName, interfaces, accessFlags);
    try {
    		// 通過 defineClass0 聲明 Class 並返回
        return defineClass0(loader, proxyName,
                            proxyClassFile, 0, proxyClassFile.length);
    } catch (ClassFormatError e) {
        /*
         * A ClassFormatError here means that (barring bugs in the
         * proxy class generation code) there was some other
         * invalid aspect of the arguments supplied to the proxy
         * class creation (such as virtual machine limitations
         * exceeded).
         */
        throw new IllegalArgumentException(e.toString());
    }
}

可以看到,ProxyClassFactory 中主要是對代理類名的確定,它主要經歷了下面的步驟:

  1. 遍歷接口列表,通過 Class.forName 加載接口對應的 Class 對象。
  2. 若存在非 public 的接口,用它們的包名作爲所有代理類的包名,並且它們的包訪問權限也會變成非 public。
  3. 若沒有非 public 的接口,使用 sun.proxy 作爲包名。
  4. proxyPkg + proxyClassNamePrefix + num 的格式構建代理類名
  5. 調用 ProxyGenerator.generateProxyClass 創建代理 Class 的 Class 文件。
  6. 通過 defineClass0 定義 Class 並返回。

ProxyGenerator

顯然,Class 文件生成的關鍵位於ProxyGenerator.generateProxyClass 中,我們首先看到 ProxyGeneratory.generateProxyClass

我的 IDEA 只能查看到 Class 文件,如果想看 Java 文件的讀者可以到這裏查看:ProxyGenerator.java

public static byte[] generateProxyClass(final String name,
                                        Class<?>[] interfaces,
                                        int accessFlags) {
    ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
    final byte[] classFile = gen.generateClassFile();

    if (saveGeneratedFiles) {
        java.security.AccessController.doPrivileged(
        new java.security.PrivilegedAction<Void>() {
            public Void run() {
                try {
                    int i = name.lastIndexOf('.');
                    Path path;
                    if (i > 0) {
                        Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar));
                        Files.createDirectories(dir);
                        path = dir.resolve(name.substring(i+1, name.length()) + ".class");
                    } else {
                        path = Paths.get(name + ".class");
                    }
                    Files.write(path, classFile);
                    return null;
                } catch (IOException e) {
                    throw new InternalError(
                        "I/O exception saving generated file: " + e);
                }
            }
        });
    }

    return classFile;
}

可以看到,它實際上是構建了一個對應參數的 ProxyGenerator 對象,並調用了其 generateClassFile 方法:

private byte[] generateClassFile() {

    // 第一步,將類中的所有法組裝爲 ProxyMethod 對象
    
    // 對於 hashCode equals toString 方法,首先調用 addProxyMethod 從而將原本的方法用 Object 的實現進行覆蓋
    addProxyMethod(hashCodeMethod, Object.class);
    addProxyMethod(equalsMethod, Object.class);
    addProxyMethod(toStringMethod, Object.class);

    // 對接口中其餘的方法調用 addProxyMethod 添加代理方法
    for (Class<?> intf : interfaces) {
        for (Method m : intf.getMethods()) {
            addProxyMethod(m, intf);
        }
    }

    // 遍歷所有生成的代理方法,確保它們的返回值是兼容的
    for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
        checkReturnTypes(sigmethods);
    }

    // 第二步,組裝生成的Class文件的FieldInfo和MethodInfo (每個方法都會生成一個static的Method對象和對應的代理方法)
    try {
        methods.add(generateConstructor());

        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
            for (ProxyMethod pm : sigmethods) {

                // 添加代理類的 static 字段
                fields.add(new FieldInfo(pm.methodFieldName,
                    "Ljava/lang/reflect/Method;",
                     ACC_PRIVATE | ACC_STATIC));

                // 添加代理方法
                methods.add(pm.generateMethod());
            }
        }

        methods.add(generateStaticInitializer());

    } catch (IOException e) {
        throw new InternalError("unexpected I/O Exception", e);
    }

		// 確保不會超過 65535 的限制
    if (methods.size() > 65535) {
        throw new IllegalArgumentException("method limit exceeded");
    }
    if (fields.size() > 65535) {
        throw new IllegalArgumentException("field limit exceeded");
    }

    // 第三步,寫入 Class 文件

    // 對常量池進行驗證
    cp.getClass(dotToSlash(className));
    cp.getClass(superclassName);
    for (Class<?> intf: interfaces) {
        cp.getClass(dotToSlash(intf.getName()));
    }

    // 常量池設置爲已讀
    cp.setReadOnly();

    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    DataOutputStream dout = new DataOutputStream(bout);

    try {
        // 寫入MagicNumber
        dout.writeInt(0xCAFEBABE);
        // 寫入minor版本號
        dout.writeShort(CLASSFILE_MINOR_VERSION);
        // 寫入major版本號
        dout.writeShort(CLASSFILE_MAJOR_VERSION);
				// 寫入常量池
        cp.write(dout);
				// 寫入 accessFlags
        dout.writeShort(accessFlags);
        // 寫入當前class索引
        dout.writeShort(cp.getClass(dotToSlash(className)));
				// 寫入父類/接口索引
        dout.writeShort(cp.getClass(superclassName));
				// 寫入接口個數
        dout.writeShort(interfaces.length);
        // 寫入接口
        for (Class<?> intf : interfaces) {
            dout.writeShort(cp.getClass(
                dotToSlash(intf.getName())));
        }
        // 寫入字段個數
        dout.writeShort(fields.size());
        // 寫入字段
        for (FieldInfo f : fields) {
            f.write(dout);
        }
				// 寫入方法個數
        dout.writeShort(methods.size());
        // 寫入方法
        for (MethodInfo m : methods) {
            m.write(dout);
        }
				// 寫入屬性表,代理類不需要屬性表(包含了代碼)
        dout.writeShort(0);

    } catch (IOException e) {
        throw new InternalError("unexpected I/O Exception", e);
    }
		// 輸出爲byte數組
    return bout.toByteArray();
}

這裏的代碼非常非常非常長,但是結構其實非常清晰,並且官方几乎對每個操作都加上了註釋,非常易於理解:

  1. 第一步,將類中的所有法組裝爲 ProxyMethod 對象
    1. 對於 hashCodeequalstoString 方法,首先調用 addProxyMethod 從而將原本的方法用 Object 的實現進行覆蓋
    2. 對接口中其餘的方法調用 addProxyMethod 添加代理方法。
    3. 遍歷所有生成的代理方法,確保它們的返回值是兼容的。
  2. 第二步,組裝生成的 Class 文件的 FieldInfoMethodInfo (每個方法都會生成一個 staticMethod 成員變量和對應的代理方法)
    1. 遍歷所有 ProxyMethod,添加代理類的 static Method 對象字段,並添加對應的代理方法。
    2. 確保方法數、字段數不會超過 65535 的限制。
  3. 第三步,將所有內容寫入 Class 文件(之前寫 ClassDecoder 的時候寫了很多類似的代碼,很有感觸哈哈,又複習了一遍 Class 文件的結構)
    1. 寫入 MagicNumber
    2. 寫入 minor 版本號
    3. 寫入 major 版本號
    4. 寫入常量池
    5. 寫入 accessFlags
    6. 寫入當前類的索引
    7. 寫入父類/接口的索引
    8. 寫入接口個數
    9. 寫入接口
    10. 寫入字段個數
    11. 寫入字段
    12. 寫入方法個數
    13. 寫入方法
    14. 寫入屬性表,由於代理類不需要屬性表,因此寫入的是 0
    15. 輸出爲 byte 數組。

關於 ProxyGeneratoraddProxyMethod 等具體實現我們就不再關注了,實際上就是通過反射解析方法的參數等信息然後創建一個對應的方法。最後生成的代理類中的每個方法都會調用到 InvokeHandler.invoke 方法。

而最後的 defineClass0 方法是一個 native 方法,估計是調用到了 JVM 底層的方法,這裏就不去關注了。

總結

JDK 中的動態代理是一套基於反射獲取接口的信息,通過對 Class 文件按字節寫入生成新的反射類文件並加載進 JVM 的動態代理框架。它有個很致命的特點就是只支持對接口的代理

一個代理對象的生成主要分兩步:

  1. 通過 WeakCache 獲取緩存的 Class 對象。其中 WeakCache 存在如下的特點:

    1. WeakCache 中只有 get 方法,當緩存中沒有存在對應的 value 時,它會通過創建時傳入的 valueFactoryvalue 進行創建。

    2. 它通過以 ClassLoaderkey 的第一級緩存以及以自定義的 subKeyFactory 生成的 subKeykey 的第二級緩存,這樣的二級緩存機制極大的加快了我們對 Map 的查找效率。

    3. WeakCache 並沒有通過加鎖來保證線程安全,而是將它的線程安全交給了 ConcurrentHashMap 來保證,並且通過大量的 CAS 操作來保證對 Map 操作的安全性。

  2. 創建接口對應的 Class 代理類對象。

    1. 通過 ProxyClassFactory 先解析接口的信息並生成對應的代理類名。
    2. 通過 ProxyGenerator 對類文件的信息進行組裝並輸出對應的 Class 文件,主要分爲以下三步:
      1. 第一步,將類中的所有法組裝爲 ProxyMethod 對象。
      2. 第二步,組裝生成的 Class 文件的 FieldInfoMethodInfo
      3. 第三步,將所有內容寫入 Class 文件

最終,代理類中所有的方法的調用都會調用到 InvocationHandler.invoke,從而實現用戶對接口的代理。

參考資料

ProxyGenerator.java

JDK動態代理——WeakCache緩存的實現機制

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章