1. 程式人生 > >JDK動態代理(2)JDK動態代理的底層實現之Proxy原始碼分析

JDK動態代理(2)JDK動態代理的底層實現之Proxy原始碼分析

JDK動態代理(2)JDK動態代理的底層實現之Proxy原始碼分析

在上一篇裡為大家簡單介紹了什麼是代理模式?為什麼要使用代理模式?並用例子演示了一下靜態代理和動態代理的實現,分析了靜態代理和動態代理各自的優缺點。在這一篇中筆者打算深入原始碼為大家剖析JDK動態代理實現的機制,建議讀者閱讀本篇前可先閱讀一下筆者上一篇關於代理模式的介紹《JDK動態代理[1]----代理模式實現方式的概要介紹》

上一篇動態代理的測試類中使用了Proxy類的靜態方法newProxyInstance方法去生成一個代理類,這個靜態方法接收三個引數,分別是目標類的類載入器,目標類實現的介面集合,InvocationHandler例項,最後返回一個Object型別的代理類。我們先從該方法開始,看看代理類是怎樣一步一步造出來的,廢話不多說,直接上程式碼

newProxyInstance方法:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h) throws IllegalArgumentException {
    //驗證傳入的InvocationHandler不能為空
    Objects.requireNonNull
(h); //複製代理類實現的所有介面 final Class<?>[] intfs = interfaces.clone(); //獲取安全管理器 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); } //獲取引數型別是InvocationHandler.class的代理類構造器 final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; //如果代理類是不可訪問的, 就使用特權將它的構造器設定為可訪問 if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } //傳入InvocationHandler例項去構造一個代理類的例項 //所有代理類都繼承自Proxy, 因此這裡會呼叫Proxy的構造器將InvocationHandler引用傳入 return cons.newInstance(new Object[]{h}); } catch (Exception e) { //為了節省篇幅, 筆者統一用Exception捕獲了所有異常 throw new InternalError(e.toString(), e); } }

可以看到,newProxyInstance方法首先是對引數進行一些許可權校驗,之後通過呼叫getProxyClass0方法生成了代理類的類物件,然後獲取引數型別是InvocationHandler.class的代理類構造器。檢驗構造器是否可以訪問,最後傳入InvocationHandler例項的引用去構造出一個代理類例項,InvocationHandler例項的引用其實是Proxy持有著,因為生成的代理類預設繼承自Proxy,所以最後會呼叫Proxy的構造器將引用傳入。在這裡我們重點關注getProxyClass0這個方法,看看代理類的Class物件是怎樣來的,下面貼上該方法的程式碼

getProxyClass0方法:

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    //目標類實現的介面不能大於65535
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    //獲取代理類使用了快取機制
    return proxyClassCache.get(loader, interfaces);
}

可以看到getProxyClass0方法內部沒有多少內容,首先是檢查目標代理類實現的介面不能大於65535這個數,之後是通過類載入器和介面集合去快取裡面獲取,如果能找到代理類就直接返回,否則就會呼叫ProxyClassFactory這個工廠去生成一個代理類。關於這裡使用到的快取機制我們留到下一篇專門介紹,首先我們先看看這個工廠類是怎樣生成代理類的。

ProxyClassFactory工廠類:

//代理類生成工廠
private static final class ProxyClassFactory 
                implements BiFunction<ClassLoader, Class<?>[], 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) {
            //這裡遍歷interfaces陣列進行驗證, 主要做三件事情
            //1.intf是否可以由指定的類載入進行載入
            //2.intf是否是一個介面
            //3.intf在陣列中是否有重複
        }
        //生成代理類的包名
        String proxyPkg = null;
        //生成代理類的訪問標誌, 預設是public final的
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
        for (Class<?> intf : interfaces) {
            //獲取介面的訪問標誌
            int flags = intf.getModifiers();
            //如果介面的訪問標誌不是public, 那麼生成代理類的包名和介面包名相同
            if (!Modifier.isPublic(flags)) {
                //生成的代理類的訪問標誌設定為final
                accessFlags = Modifier.FINAL;
                //獲取介面全限定名, 例如:java.util.Collection
                String name = intf.getName();
                int n = name.lastIndexOf('.');
                //剪裁後得到包名:java.util
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                //生成的代理類的包名和介面包名是一樣的
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    //代理類如果實現不同包的介面, 並且介面都不是public的, 那麼就會在這裡報錯
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }
        //如果介面訪問標誌都是public的話, 那生成的代理類都放到預設的包下:com.sun.proxy
        if (proxyPkg == null) {
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }
        //生成代理類的序號
        long num = nextUniqueNumber.getAndIncrement();
        //生成代理類的全限定名, 包名+字首+序號, 例如:com.sun.proxy.$Proxy0
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
        //這裡是核心, 用ProxyGenerator來生成位元組碼, 該類放在sun.misc包下
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName,
                                  interfaces, accessFlags);
        try {
            //根據二進位制檔案生成相應的Class例項
            return defineClass0(loader, proxyName, proxyClassFile, 
                              0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            throw new IllegalArgumentException(e.toString());
        }
    }
}

該工廠的apply方法會被呼叫用來生成代理類的Class物件,由於程式碼的註釋比較詳細,我們只挑關鍵點進行闡述,其他的就不反覆贅述了。

  1. 在程式碼中可以看到JDK生成的代理類的類名是“$Proxy”+序號。

  2. 如果介面是public的,代理類預設是public final的,並且生成的代理類預設放到com.sun.proxy這個包下。

  3. 如果介面是非public的,那麼代理類也是非public的,並且生成的代理類會放在對應介面所在的包下。

  4. 如果介面是非public的,並且這些介面不在同一個包下,那麼就會報錯。

生成具體的位元組碼是呼叫了ProxyGenerator這個類的generateProxyClass方法。這個類放在sun.misc包下,後續我們會扒出這個類繼續深究其底層原始碼。到這裡我們已經分析了Proxy這個類是怎樣生成代理類物件的,通過原始碼我們更直觀的瞭解了整個的執行過程,包括代理類的類名是怎樣生成的,代理類的訪問標誌是怎樣確定的,生成的代理類會放到哪個包下面,以及InvocationHandler例項的引用是怎樣傳入的。不過讀者可能還會有疑問,WeakCache快取是怎樣實現的?為什麼proxyClassCache.get(loader, interfaces)最後會呼叫到ProxyClassFactory工廠的apply方法?在下一篇中將會為讀者詳細介紹WeakCache快取的實現原理。

原文連結:https://www.cnblogs.com/liuyun1995/p/8157098.html