面試官:為什麼jdk動態代理只能代理介面實現類?

語言: CN / TW / HK

**本文首發於公眾號【看點程式碼再上班】,歡迎圍觀,第一時間獲取最新文章。**

原文地址:https://mp.weixin.qq.com/s/LWToCjGGP52_0cy9xkiHlQ

大家好,我是tin,這是我的第15篇原創文章

我們日常業務能夠使用jdk動態代理編碼的場景非常少,但是我們使用的框架用到jdk動態代理的卻非常多,今天結合jdk動態代理原始碼講一講“為什麼jdk動態代理只能代理介面?”,先上一個目錄:

一、把jdk動態代理跑起來

顧名思義,jdk動態代理是jdk實現的一個功能,不需要第三方庫支援,我們配置好依賴jdk到我們的工程即可使用,要把jdk動態代理跑起來,非常簡單:

  • 業務目標物件實現介面;

  • 實現InvocationHandler介面;

  • 使用Proxy.newProxyInstance生成代理物件;

定義業務BookFacade介面:

BookFacadeImpl介面實現:

定義MyInvocationHandler實現InvocationHandler介面:

package com.tin.example.jdk.proxy;
​
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
​
/**
 * title: MyInvocationHandler
 * <p>
 * description:
 *
 * @author tin @公眾號【看點程式碼再上班】 on 2022/1/22 上午9:01
 */
public class MyInvocationHandler implements InvocationHandler {
    //目標物件
    private Object target;
​
    public Object getInstance(Object target) {
        this.target = target;
        Class clazz = this.target.getClass();
        // Proxy.newProxyInstance的三個引數分別是:
        // 1 被代理類的類載入器
        // 2 被代理類的介面
        // 3 java.lang.reflect.InvocationHandler
        return Proxy.newProxyInstance(clazz.getClassLoader(),
                clazz.getInterfaces(),
                this);
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("------前置通知------------");
        // 執行目標方法
        Object result = method.invoke(target, args);
        System.out.println("------後置處理------------");
        return result;
    }
}

MyInvocationHandler類內的Proxy.newProxyInstance生成代理物件。通過以上類定義,我們來寫一個main測試類:

package com.tin.example.jdk.proxy;
​
/**
 * title: JdkProxyTest
 * <p>
 * description:
 *
 * @author tin @公眾號【看點程式碼再上班】 on 2022/1/22 上午9:11
 */
public class JdkProxyTest {
    public static void main(String[] args) throws Exception {
        //sun.misc.ProxyGenerator.saveGeneratedFiles 用於輸出代理類class檔案到本地
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
​
        //代理介面
        BookFacade proxy = (BookFacade) new MyInvocationHandler().getInstance(new BookFacadeImpl());
        proxy.addBook("Java效能權威指南@【看點程式碼再上班】");
    }
}

main方法執行結果如下:

因為我的測試類已經加了以下程式碼:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

這是用於把生成的proxy代理類的class檔案輸出到本地的,開啟生成的class檔案,內容如下:

同樣的,生成的代理類實現了目標介面定義的方法addBook():

到這裡就可以初步回答我們標題的問題了:

jdk動態代理生成的代理類實現了我們業務定義的介面,並重寫了我們介面的方法,如此才實現代理的功能,所以我們的目標類需要實現介面。

二、復現問題“jdk動態代理不能代理普通類”

看了以上示例估計就有朋友反問了,所舉例子只是一個特例,說明介面實現類可以被jdk代理,非介面實現類也可以吧,不一定是通過"implements"方式實現,這就是我們這小節的問題“jdk動態代理能不能代理普通類?”。

新建一個沒有實現介面的類BookOperation:

public class BookOperation {
    public void addBook(String bookName) {
        System.out.println("新增書籍, bookName:" + bookName);
    }
}

Test測試類如下:

public class JdkProxyTest {
    public static void main(String[] args) throws Exception {
        //sun.misc.ProxyGenerator.saveGeneratedFiles 用於輸出代理類class檔案到本地
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        //代理普通類
        BookOperation bookOperation = (BookOperation) new MyInvocationHandler().getInstance(new BookOperation());
        bookOperation.addBook("Java效能權威指南@【看點程式碼再上班】");
    }
}

依然通過類強轉的方式獲取生成的類:

BookOperation bookOperation = (BookOperation) new MyInvocationHandler().getInstance(new BookOperation());

有朋友可能會反問,它不一定是生成BookOperation類,這樣取是不是不合適的?是的,這樣的提問是沒問題的,但是如果不強轉,我不知道如何能夠呼叫我們的目標方法。

為了能夠驗證需要型別強轉且確確實實無法代理非介面實現類,我們看下面的執行結果以及生成的代理類的結構。

執行結果如下:

從拋的異常來看,代理類Proxy已經生成,但不是我想要的BookOperation的代理類!型別強轉失敗。

既然不是我指定的目標類的代理類,那麼生成的Proxy類究竟是怎麼樣的呢?

見下圖:

和我們前面的Proxy最大的不同在於沒有實現介面(因為BookOperation沒有實現介面,這是必然的), 整個Proxy內也沒有addBook()方法。

既然沒有目標類的方法,代理類如何執行我們的目標方法?

既然沒有介面,那為什麼不能繼承BookOperation類完成代理功能?! ”會有人這麼想。

這樣想就對了,這個也就是cglib採用的方式,具體原理請閱讀以下文章啦:

面試官:cglib為什麼不能代理private方法?

到這裡,我們可以再次確定標題所問之答案:jdk動態代理無法代理非介面實現類。

三、深入原始碼分析

以上只是通過表層分析我們的jdk動態代理無法代理非介面實現類。是否真如此呢?我們通過原始碼看一看。

jdk代理的關鍵在代理類的生成。我們從Proxy.newProxyInstance()方法切入,深入分析代理類是如何生成的。

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
{
        Objects.requireNonNull(h);
​
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
​
        /*
         * Look up or generate the designated proxy class.
         * ① 尋找或者生成(如果快取沒有)指定代理類
         */
        Class<?> cl = getProxyClass0(loader, intfs);
​
        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
        /*
         * ② 獲取構造器
         */
            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;
                    }
                });
            }
         /*
         * ③ 根據我們定義的invocation handler生成代理物件
         */
            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);
        }
    }

Proxy.newProxyInstance()關鍵在兩步:

  • ① Class<?> cl = getProxyClass0(loader, intfs) 生成代理類;

  • ② return cons.newInstance(new Object[]{h}) 生成代理物件;

第①點,生成代理類,就是生成我們以上com/sun/proxy/$Proxy0.class代理類的位元組碼,繼續跟進去看一下。

getProxyClass0方法程式碼很少,其內部直接呼叫了proxyClassCache.get方法:

proxyClassCache是一個本地靜態變數也是一個快取cache,其構造器的兩個入參KeyFactory和ProxyClassFactory至關重要,分別對應 keyFactory和valueFactory

繼續進入到proxyClassCache.get()方法內部,如下:

看上圖的第③點,獲取最終的代理類value值重點就在於Factory的get()方法:

前面講proxyClassCache的構造器入參時已經提到valueFactory對應的就是ProxyClassFactory。valueFactory.apply其實對應的是ProxyClassFactory#apply。

我們繼續跟進去看看ProxyClassFactory#apply方法邏輯(ProxyClassFactory也是Proxy的一個內部類):

第①點,

interfaceClass = Class.forName(intf.getName(), false, loader);

驗證入參類載入器按全限定名稱重新載入入參介面得到的class物件是否和介面class物件相同,主要也是為了驗證類載入器是否相同,因為同一個類載入器載入的class才相同,否則不相同:

第①點,

if (!interfaceClass.isInterface())

驗證入參class物件是否是介面。

其實,如果目標類沒有實現介面,interfaces陣列會為空,不會走到這裡,我們啟動非介面實現類debug如下:

interfaces為空,會導致最終生成的代理類缺少相關的方法。繼續跟進到第③點 生成代理類 一看究竟。

sun.misc.ProxyGenerator#generateProxyClass(java.lang.String, java.lang.Class<?>[], int)方法如下:

最終生成class是在generateClassFile(), 該方法生成類的方法和屬性等資訊,它會遍歷interfaces中的每一個介面class,把class的每一個方法包裝為ProxyMethod物件,最後通過ProxyMethod生成代理類的代理方法並輸出到位元組流中,如下圖:

從以上圖示可以很明顯的看出來,非介面實現類傳的引數interfaces是一個空陣列,最後generateClassFile()無法生成對應的代理方法。

以上,從原始碼維度也佐證了jdk動態代理依賴介面,只有實現了介面的類的方法才能被加入到代理類中生成代理方法,最終完成代理功能。

四、結語

我是tin,一個在努力讓自己變得更優秀的普通工程師。自己閱歷有限、學識淺薄,如有發現文章不妥之處,非常歡迎加我提出,我一定細心推敲並加以修改。

堅持創作不容易,你的正反饋是我堅持輸出的最強大動力,謝謝!

最後別忘了關注我公眾號哦【看點程式碼再上班】!附上原文連結:arrow_double_down::arrow_double_down::arrow_double_down:

https://mp.weixin.qq.com/s/LWToCjGGP52_0cy9xkiHlQ