Java代理機制分析——JDK代理(Proxy、InvocationHandler與示例)

psycho7ogist發表於2020-10-21

前言

  • Java的代理機制使得我們可以在不修改原始的Java物件的程式碼的情況下,對原始的Java物件進行功能的增強。
  • Java的代理機制包括了靜態代理和動態代理,靜態代理需要我們為每個需要被代理的物件編寫對應的代理類,當被代理物件較多時,使用靜態代理就不太合適了,而動態代理使得我們可以把需要被代理的物件作為引數,傳遞給用於生成代理物件的方法,並在執行時動態地建立代理物件。
  • 動態代理又包括了使用JDK api實現的JDK代理和使用cglib包實現的cglib代理,JDK代理需要被代理物件實現了介面,通過invocationHandler呼叫原物件方法實現代理,而cglib不需要被代理物件實現介面,通過繼承原物件並重寫位元組碼的方式實現代理。
  • 本文介紹了JDK代理的機制以及簡單的使用過程,JDK代理的核心包括Proxy類和InvocationHandler介面。
  • 如圖為Proxy類的UML圖。
    java.lang.reflect.Proxy UML圖

1 核心——Proxy類

1.1 變數/域

1.1.1 proxyClassCache

  • 型別:WeakCache<ClassLoader, Class<?>[], Class<?>>
  • 目的:快取生成的代理類的class物件的工廠物件,並通過快取的工廠物件產生代理類的class物件。
  • 原理:在Proxy呼叫newProxyInstance方法時,會首先在快取中查詢是否存在對應代理類的class物件,若找到了則可以直接利用該class物件,生成並返回新的代理物件;否則會利用ProxyClassFactory建立新的代理類的class物件(並放入快取),生成並返回新的代理物件。proxyClassCache通過一個靜態的WeakCache快取實現,WeakCache通過巢狀的concurrentMap實現二級快取:一級快取的key(key)是使用傳入的ClassLoader包裝而成的CacheKey(繼承於WeakReference,見下);二級快取的key(subkey)是使用傳入的介面class物件陣列通過KeyFactory(見1.3.1)生成的,而二級快取(valuesMap)的value是一個實現了supplier介面(見下)的物件。該物件是一個用於產生目標代理類class物件的工廠,對該物件呼叫get方法來獲取真正的代理類的class物件。此外,在WeakCache中使用到了弱引用類WeakReference和一個弱引用佇列ReferenceQueue解決concurrentMap的垃圾回收問題。
  • supplier介面:泛型函式式介面,通過重寫get方法,可以返回一個物件。
  • 為什麼要使用WeakReference和ReferenceQueue:要解決這個問題,首先需要知道什麼是強引用和弱引用。以本例來說,當我們直接向concurrentMap中存放一個鍵值對時,我們向map中新增了三個物件的引用,鍵值對的entry(node),key和value,引用預設是強引用,意味著當我們沒有顯式地從map中remove(key)時,這三個物件是不會被垃圾回收的。因此如果程式長時間執行而沒有顯式remove,map中加入了越來越多的物件,就可能造成效能問題。與強引用不同的是,如果一個物件只存在弱引用,那麼它在下一次垃圾回收時,就會被回收。因此,在這裡使用了WeakReference弱引用包裝類和ReferenceQueue弱引用佇列來解決物件回收的問題。具體的做法是,將map的key包裝到WeakReference中,實現key到其他物件的弱引用,但是如果只有key使用了弱引用是不夠的,因為就算key被垃圾回收了,entry和value仍然沒有被回收。利用ReferenceQueue可以解決這個問題,WeakReference在建立的時候需要兩個引數,一個是被包裝的key,另一個則是一個指定的ReferenceQueue,key被註冊到該ReferenceQueue中。當key被回收時,會向該ReferenceQueue中加入這個key。這時會產生一個看似矛盾的問題,為什麼想要key被回收,但是又要在它被回收的時候加入佇列中?這是因為在key被回收時,我們還需要通過它去清除map中的整個entry物件。在本例中,concurrentMap每次呼叫get方法時,都會執行一次清除工作,去清除存在於自身中且與ReferenceQueue中的key對應的entry,key和value,這樣就可以及時清理掉一級快取concurrentMap中過期的物件。

1.2 方法

1.2.1 newProxyInstance(核心)

  • 目的:獲取新的代理物件。
  • 原理:根據傳入的類載入器、介面class物件的陣列和invocationHandler實現類,從proxyClassCache中獲取一個實現了傳入的介面方法的代理類的class物件,隨後通過反射的方式獲取class物件中的constructor,呼叫newInstance方法生成代理物件例項並返回。

1.2.2 getProxyClass

  • 目的:從proxyClassCache中根據類載入器和介面class物件的陣列返回代理類的class物件(比newProxyInstance少一個通過反射生成代理物件的步驟)。

1.2.3 defineClass0(核心)

  • 目的:生成新的代理類的class物件。
  • 原理:defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length),通過方法生成的新的代理類的class物件中,成員變數/例項域中包含了指定的介面中的所有方法的method物件和後期通過構造方法傳入的invocationHandler實現類,成員方法中實現了指定的介面中的所有方法。在外部呼叫代理物件的方法時,這些方法內部實際上是通過代理物件中的invocationHandler,轉而呼叫invoke(Object proxy, Method method, Object[] args),其中proxy代表代理物件自身this,method是代理物件方法對應的成員變數method物件,args是method所需引數。

1.3 內部類

1.3.1 KeyFactory

  • 目的:生成proxyClassCache中二級快取(valuesMap)的鍵,該鍵標識了傳入的一組class物件。
  • 原理:KeyFactory是BiFunction介面的實現類(泛型函式式介面,通過重寫apply方法,可以傳入兩個物件,運算然後返回一個結果物件),泛型引數為BiFunction<ClassLoader, Class<?>[], Object>,表示可以傳入一個類載入器和一個介面class物件的陣列,通過apply方法**生成一個Key?物件**(見1.3.3),並返回。KeyFactory實際上並沒有呼叫classLoader,僅僅通過class<?>[]的長度判斷返回哪一種Key。

1.3.2 ProxyClassFactory

  • 目的:生成新的代理類的class物件。
  • 原理:ProxyClassFactory是BiFunction介面的實現類,泛型引數為BiFunction<ClassLoader, Class<?>[], Class<?>>,表示可以傳入一個類載入器和一個介面class物件的陣列,通過apply方法生成一個代理類的class物件,並返回。apply方法中包括了生成代理類class物件的一些預處理,如檢查傳入的class物件陣列中的class物件是否被類載入器可見、是否是介面、生成新的代理類的類名proxyName等,最後呼叫native的defineClass0方法(見)生成新的代理類的class物件,該class物件隨後用於生成代理類例項。ProxyClassFactory的apply方法在對proxyClassCache的二級快取的value——工廠物件呼叫get方法時呼叫。

1.3.3 Key1,Key2,KeyX

  • 目的:作為proxyClassCache二級快取的鍵。
  • 原理:KeyFactory在生成Key?物件時,這個物件的實際型別由傳入的介面class物件的陣列大小決定,陣列中大於2個class物件時生成KeyX類的物件/例項,而只有1個(最常見)或2個class物件時分別生成Key1類和Key2類的物件,這樣做是因為針對後兩種的情況進行了程式碼的優化。鍵與鍵之間也是用hashcode來比較的。

2 核心——InvocationHandler介面

2.1 目的

實現了讓代理物件在呼叫介面方法時,轉而執行增強的、被代理過的方法——invoke(Object proxy, Method method, Object[] args)。

2.1 原理

該介面的實現類相當於一個方法呼叫處理器,在Proxy.newProxyInstance呼叫時被傳入到Proxy物件中。隨後,在Proxy產生了代理類的class物件,並通過反射生成代理物件時,被傳入到代理物件中。因此,代理物件呼叫任何方法時,都會使用invocationHandler執行定義的invoke方法。

3 示例

3.1 InvocationHandler實現類

class InvocationHandlerImpl implements InvocationHandler {
    Object targetObject;

    public InvocationHandlerImpl(Object targetObject) {
        this.targetObject = targetObject;
    }
	
	// 由3.4 可知,proxy代表代理物件本身(此處沒有使用),method代表被代理介面中當前被呼叫的method物件,args為method物件引數
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // your code before method invoked
        System.out.println("start to do something..");
        Object result = method.invoke(targetObject, args);
        // your code after method invoked
        System.out.println("end to do something..");
        return result;
    }
}

3.2 被代理的介面

interface Person {
    void eat();

    void drink();
}

3.3 main方法中建立介面的代理物件

public static void main(String[] args) {
		// JDK代理只能代理實現了介面的物件,建立這個物件傳入invocationHandler實現類中,後者呼叫invoke方法時,method的反射呼叫會使用到該物件
        Person person = new Person() {
            @Override
            public void eat() {
                System.out.println("eating...");
            }

            @Override
            public void drink() {
                System.out.println("drinking...");
            }
        };
        Person proxyPerson = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, new InvocationHandlerImpl(person));
        proxyPerson.eat();
        // 列印 start to do something..
		//      eating...
  		//      end to do something..
    }

3.4 Proxy中自動建立的代理類class物件(簡要)

public class $Proxy0 {
    InvocationHandler invocationHandler;
    Method originalEat = xxx; // 此處為介面原方法的method物件,用於invocationHandler實現類的invoke方法通過反射來呼叫!
    Method originalDrink = xxx;
	
	public $Proxy0(InvocationHandler invocationHandler){
		this.invocationHandler = invocationHandler;
	}
	
	// 實現了介面方法,並在每個方法中通過invocationHandler呼叫增強的方法
    public void eat() throws Throwable {
        invocationHandler.invoke(this, originalEat, null);
    }

    public void drink() throws Throwable {
        invocationHandler.invoke(this, originalDrink, null);
    }
}

參考

  • https://blog.csdn.net/jiankunking/article/details/52143504
  • https://www.cnblogs.com/liuyun1995/p/8144676.html
  • https://zhuanlan.zhihu.com/p/88759564
  • https://segmentfault.com/a/1190000011291179

相關文章