java動態代理基本原理及proxy原始碼分析一

tera發表於2020-07-08

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

為了儘快進入正題,這裡先跳過spring aop和java動態代理的使用流程的講解,這部分內容後面再單獨寫文章整理

 

不過,我們首先還是先看下java dynamic proxy的基本使用方法,假定我們要代理的物件是一個Map,則程式碼如下:

Map proxyInstance = (Map) Proxy.newProxyInstance(
                HashMap.class.getClassLoader(),
                new Class[]{Map.class},
                new DynamicInvocationHandler());

之後proxyInstance就可以作為一個正常的Map物件進行使用了

為了對生成物件的屬性做一個基本的瞭解,我們先列印一下proxyInstance的實際型別名稱

System.out.println(proxyInstance.getClass().getName());

得到結果

com.sun.proxy.$Proxy11

如果使用多了,就會發現所有的代理類的名稱都是$Proxy加一個數字,且包名是com.sun.proxy

 

當我們檢視Proxy.newProxyInstance方法時,會發現它返回的其實是一個Object物件

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

而在實際使用的過程中,它是可以被直接轉型成我們傳入的介面型別,因此可以推測出,該proxyInstance物件的實際型別肯定是實現了我們傳入的介面

我們列印一下該類實現的介面

for (Class intf : proxyInstance.getClass().getInterfaces()) {
    System.out.println(intf.getName());
}

得到結果

java.util.Map

符合我們之前的推測

 

接著我們再列印一下該類的父類

System.out.println(proxyInstance.getClass().getSuperclass().getName());

得到結果

java.lang.reflect.Proxy

 

因此總結一下,該proxyInstance物件有以下3個屬性
1.繼承了Proxy類
2.實現了我們傳入的介面
3.以$Proxy+隨機數字的命名

那麼動態生成代理類的功能究竟是如何實現的呢?接下去就來看java的原始碼
因為原始碼有點多,所以我只貼出關鍵的部分

入口自然是Proxy.newProxyInstance方法
其中有2個部分我們需要關心

第一部分,類的建立

/*
 * Look up or generate the designated proxy class.
 */
Class<?> cl = getProxyClass0(loader, intfs);

這個就是實際生成類的方法,後面我們會繼續深究,先略放一放

 

第二部分,例項的建立

final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
... 
return cons.newInstance(new Object[]{h});

最終物件的例項化過程就是通過之前生成的class,獲取其指定引數的建構函式,並將InvocationHandler物件傳入

檢視constructorParams欄位

/** parameter types of a proxy class constructor */
private static final Class<?>[] constructorParams =
        { InvocationHandler.class };

的確就是獲取InvocationHandler物件的一個建構函式

回想一下之前類定義的第一條,繼承了Proxy類,因此我們去Proxy類中找一下

    /**
     * Constructs a new {@code Proxy} instance from a subclass
     * (typically, a dynamic proxy class) with the specified value
     * for its invocation handler.
     *
     * @param  h the invocation handler for this proxy instance
     *
     * @throws NullPointerException if the given invocation handler, {@code h},
     *         is {@code null}.
     */
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

在該建構函式中就是將引數h賦值給了成員變數h,這裡名稱h可以記一下,在之後的文章中還會遇到

 

看完例項的建立,讓我們回到更重要的第一部分,類的生成
進入getProxyClass0(loader, intfs)方法

    /**
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     */
    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);
    }

該方法很簡單,直接從一個cache中拿取物件

檢視proxyClassCache物件

    /**
     * a cache of proxy classes
     */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

該物件本質就是一個類似於Map的快取,不過使用的是WeakCache,這個WeakCache本身的特性我們放到另一篇文章中討論,本文專注於Proxy
我們可以看到該快取的建構函式獲取了2個Factory,顧名思義,第一個是生成key的,第二個是生成ProxyClass的,自然我們需要繼續看第二個Factory

類的註解如下

/**
* A factory function that generates, defines and returns the proxy class given
* the ClassLoader and array of interfaces.
*/
private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>

這個就是我們要尋找的負責具體生成類的工廠了,檢視其apply方法

首先其會對傳入的介面型別做一些校驗,包括loader能否載入到傳入的介面,介面是否實際上是介面(因為陣列的型別是Class),介面是否有重複

            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                /*
                 * Verify that the class loader resolves the name of this
                 * interface to the same Class object.
                 */
                Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                /*
                 * Verify that the Class object actually represents an
                 * interface.
                 */
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /*
                 * Verify that this interface is not a duplicate.
                 */
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }

接著設定類的預設access_flag,public final

int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

接著檢查傳入的介面陣列中是否包含非public的介面,如果有,則生成的類需要和該介面處於同一個package,且訪問屬性會去掉public,只保留final。如果有多個不同package中的非public介面,則報錯
(具體原因大家應該都可以理解)

            /*
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
            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類,則會使用預設的package名,即com.sun.proxy

            if (proxyPkg == null) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

然後獲取一個靜態自增的int

            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();

固定的類名字首

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

將上面三者組合成最終的類名(回想之前我們列印出的例項的類名)

String proxyName = proxyPkg + proxyClassNamePrefix + num;

上面這幾個步驟確定了類的名稱,但還是皮毛,接下去是生成類的血肉:位元組碼

            /*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);

具體的探究也先放一下,先看位元組碼轉換成具體類的方法

            try {
                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());
            }

而該方法是一個native的方法,所以暫時就無法繼續探究了,不過知道了這個方法後,如果我們自己有需要,也可以利用這種機制實現自己的動態類生成,後面會想辦法做一個demo,本文就不做探討了

private static native Class<?> defineClass0(ClassLoader loader, String name,
                                                byte[] b, int off, int len);

 

 


 

之前其實都是開胃菜,現在回到之前生成位元組碼的方法,檢視方法原始碼

    public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
        final byte[] var4 = var3.generateClassFile();
        if (saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    try {
                        int var1 = var0.lastIndexOf(46);
                        Path var2;
                        if (var1 > 0) {
                            Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                            Files.createDirectories(var3);
                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                        } else {
                            var2 = Paths.get(var0 + ".class");
                        }

                        Files.write(var2, var4, new OpenOption[0]);
                        return null;
                    } catch (IOException var4x) {
                        throw new InternalError("I/O exception saving generated file: " + var4x);
                    }
                }
            });
        }

        return var4;
    }

中間if部分的程式碼可以先忽略,不過我們會在後面的文章中使用到這部分功能,這裡先關注下面這2行程式碼

        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
        final byte[] var4 = var3.generateClassFile();

這裡讓我們記一下
var0是類名
var1是介面
var3是access_flag
後面我會盡量將這些varX轉換成更實際的命名,方便大家理解

之後就是本文的最終的重點,也是難點,即二進位制位元組碼的實際生成過程,包括jvm操作指令,所以我們需要先對class檔案的結構和jvm操作指令有一個瞭解,見下篇文章

總結而言:java動態代理的基本原理就是在執行時生成位元組碼,並通過一個native方法將其轉換成Class物件供我們使用

相關文章