Java動態代理——java動態代理基本原理及proxy源碼分析一


本系列文章主要是博主在學習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操作指令有一個了解,見下篇文章

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

總結而言:java動態代理的基本原理就是在運行時生成字節碼,並通過一個native方法將其轉換成Class對象供我們使用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM