Java代理機制分析——JDK代理(Proxy、InvocationHandler與示例)
前言
- Java的代理機制使得我們可以在不修改原始的Java物件的程式碼的情況下,對原始的Java物件進行功能的增強。
- Java的代理機制包括了靜態代理和動態代理,靜態代理需要我們為每個需要被代理的物件編寫對應的代理類,當被代理物件較多時,使用靜態代理就不太合適了,而動態代理使得我們可以把需要被代理的物件作為引數,傳遞給用於生成代理物件的方法,並在執行時動態地建立代理物件。
- 動態代理又包括了使用JDK api實現的JDK代理和使用cglib包實現的cglib代理,JDK代理需要被代理物件實現了介面,通過invocationHandler呼叫原物件方法實現代理,而cglib不需要被代理物件實現介面,通過繼承原物件並重寫位元組碼的方式實現代理。
- 本文介紹了JDK代理的機制以及簡單的使用過程,JDK代理的核心包括Proxy類和InvocationHandler介面。
- 如圖為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
相關文章
- Java JDK Proxy和CGLib動態代理示例講解JavaJDKCGLib
- Java進階 | Proxy動態代理機制詳解Java
- 動態代理jdk的Proxy與spring的CGlibJDKSpringCGLib
- Java的代理機制Java
- Proxy invocationHandler
- 深入理解JDK動態代理機制JDK
- Java代理(jdk靜態代理、動態代理和cglib動態代理)JavaJDKCGLib
- Java提高班(六)反射和動態代理(JDK Proxy和Cglib)Java反射JDKCGLib
- 《Proxy系列專題》:代理模式(靜態、JDK、CGLib)模式JDKCGLib
- Java中的代理模式(Proxy Pattern)Java模式
- 代理模式(Proxy)模式
- Java動態代理和反射機制Java反射
- java動態代理——代理方法的假設和驗證及Proxy原始碼分析五Java原始碼
- 代理模式詳解:靜態代理、JDK動態代理與Cglib動態代理模式JDKCGLib
- Java架構-Java JDK 動態代理Java架構JDK
- 深入理解靜態代理與JDK動態代理JDK
- 代理(Proxy)的解析
- 代理模式(Proxy Pattern)模式
- Java動態代理(JDK和cglib)JavaJDKCGLib
- java動態代理基本原理及proxy原始碼分析一Java原始碼
- 反向代理與正向代理差異分析
- JDK動態代理和CGLib代理JDKCGLib
- JDK動態代理和 CGLIB 代理JDKCGLib
- 資料劫持Object.defineProperty與代理ProxyObject
- 說透設計模式-代理模式與Proxy設計模式
- Java-JDK動態代理(AOP)使用及實現原理分析JavaJDK
- Java代理設計模式(Proxy)的四種具體實現:靜態代理和動態代理Java設計模式
- JDK動態代理物件與被代理物件地址值問題JDK物件
- VUE 未來代理操作:ES6 Proxy代理Vue
- SpringBoot應用篇之FactoryBean及代理實現SPI機制示例Spring BootBean
- java23中設計模式–代理模式ProxyJava設計模式
- java23中設計模式--代理模式ProxyJava設計模式
- SOCKS代理與HTTP代理主要區別分析HTTP
- Jdk代理和CGLIB代理的區別JDKCGLib
- 基於JDK的動態代理原理分析JDK
- 一文學會 Java 動態代理機制Java
- JDK動態代理JDK
- 技術分享:Proxy-Pool代理池搭建IP代理