極客時間第一講 動態代理是基於什麼原理。

編程語言通常有各種不同的分類角度,動態類型和靜態類型就是其中種分類角度,簡單區分就是語言類型信息是在運行時檢查,還是編譯檢查。與其近似的還有一個對比,就是所謂強類型和弱類型,就是不同類型量賦值時,是否需要顯式地(強制)進行類型轉換。那麼,如何分類Java語言呢?通常認爲,Java是靜態的強類型語言,是因爲提供了類似反射等機制,也具備了部分動態類型語言的能力。言歸正傳,今天我要問你的問題是,談談Java反射機制,動態代理是於什麼原理?典型回答反射機制是Java語言提供的一種基礎功能,賦予程序在運行時自省(introspect,官方用語)的能力。通過反射我們可以直接操作類或者象,比如獲取某個對象的類定義,獲取類聲明的屬性和方法,調用方或者構造對象,甚至可以運行時修改類定義。動態代理是一種方便運行時動態構建代理、動態處理代理方法調用的制,很多場景都是利用類似機制做到的,比如用來包裝RPC調用、面切面的編程(AOP)。實現動態代理的方式很多,比如JDK自身提供的動態代理,就是主要用了上面提到的反射機制。還有其他的實現方式,比如利用傳說中更性能的字節碼操作機制,類似ASM、cglib(基於ASM)、Javassist等考點分析這個題目給我的第一印象是稍微有點誘導的嫌疑,可能會下意識地以動態代理就是利用反射機制實現的,這麼說也不算錯但稍微有些不全面。功能纔是目的,實現的方法有很多。總的來說,這道題目考察的Java語言的另外一種基礎機制: 反射,它就像是一種魔法,引入運行自省能力,賦予了Java語言令人意外的活力,通過運行時操作元數據對象,Java可以靈活地操作運行時才能確定的信息。而動態代理,則延伸出來的一種廣泛應用於產品開發中的技術,很多繁瑣的重複編程都可以被動態代理機制優雅地解決。從考察知識點的角度,這道題涉及的知識點比較龐雜,所以面試官能擴展或者深挖的內容非常多,比如:考察你對反射機制的瞭解和掌握程度。動態代理解決了什麼問題,在你業務系統中的應用場景是什麼?JDK動態代理在設計和實現上與cglib等方式有什麼不同,進而如取捨?這些考點似乎不是短短一篇文章能夠囊括的,我會在知識擴展部分盡梳理一下。知識擴展1.反射機制及其演進對於Java語言的反射機制本身,如果你去看一下java.lang或java.lang.reflect包下的相關抽象,就會有一個很直觀的印象了。ClassField、Method、Constructor等,這些完全就是我們去操作類和對象的數據對應。反射各種典型用例的編程,相信有太多文章或書籍進行過細的介紹,我就不再贅述了,至少你需要掌握基本場景編程,這裏是方提供的參考文檔:https://docs.oracle.com/javase/tutorial/reflect/index.html 。關於反射,有一點我需要特意提一下,就是反射提供的AccessibleObject.setAccessible(boolean flag)。它的子類也大都重寫了這個方法,這裏的所謂accessible可以理解成修飾成員的public、protected、private,這意味着我們可以在運行時修改成員訪問限制!setAccessible的應用場景非常普遍,遍佈我們的日常開發、測試、依注入等各種框架中。比如,在O/R Mapping框架中,我們爲一個Java實體對象,運行時自動生成setter、getter的邏輯,這是加載或者持久化據非常必要的,框架通常可以利用反射做這個事情,而不需要開發者動寫類似的重複代碼另一個典型場景就是繞過API訪問控制。我們日常開發時可能被迫要用內部API去做些事情,比如,自定義的高性能NIO框架需要顯式地釋放DirectBuffer,使用反射繞開限制是一種常見辦法。但是,在Java 9以後,這個方法的使用可能會存在一些爭議,因爲Jigsaw項目新增的模塊化系統,出於強封裝性的考慮,對反射訪問進了限制。Jigsaw引入了所謂Open的概念,只有當被反射操作的模塊和定的包對反射調用者模塊Open,才能使用setAccessible;否則,被認是不合法(illegal)操作。如果我們的實體類是定義在模塊裏面,我需要在模塊描述符中明確聲明:module MyEntities {// Open for reflectionopens com.mycorp to java.persistence}因爲反射機制使用廣泛,根據社區討論,目前,Java 9仍然保留了兼Java 8的行爲,但是很有可能在未來版本,完全啓用前面提到的針對setAccessible的限制,即只有當被反射操作的模塊和指定的包對反射用者模塊Open,才能使用setAccessible,我們可以使用下面參數顯式置。--illegal-access={ permit | warn | deny }2.動態代理前面的問題問到了動態代理,我們一起看看,它到底是解決什麼問題首先,它是一個代理機制。如果熟悉設計模式中的代理模式,我們會道,代理可以看作是對調用目標的一個包裝,這樣我們對目標代碼的用不是直接發生的,而是通過代理完成。其實很多動態代理場景,我爲也可以看作是裝飾器(Decorator)模式的應用,我會在後面的專欄計模式主題予以補充通過代理可以讓調用者與實現者之間解耦。比如進行RPC調用,框架部的尋址、序列化、反序列化等,對於調用者往往是沒有太大意義的通過代理,可以提供更加友善的界面。代理的發展經歷了靜態到動態的過程,源於靜態代理引入的額外工作類似早期的RMI之類古董技術,還需要rmic之類工具生成靜態stub等種文件,增加了很多繁瑣的準備工作,而這又和我們的業務邏輯沒有系。利用動態代理機制,相應的stub等類,可以在運行時生成,對應調用操作也是動態完成,極大地提高了我們的生產力。改進後的RM經不再需要手動去準備這些了,雖然它仍然是相對古老落後的技術,來也許會逐步被移除這麼說可能不夠直觀,我們可以看JDK動態代理的一個簡單例子。下只是加了一句print,在生產系統中,我們可以輕鬆擴展類似邏輯進行斷、限流等。public class MyDynamicProxy {public static void main (String[] args) {HelloImpl hello = new HelloImpl();MyInvocationHandler handler = new MyInvocationHandler(// 構造代碼實例Hello proxyHello = (Hello) Proxy.newProxyInstance(Hell// 調用代理方法proxyHello.sayHello();}}interface Hello {void sayHello();}class HelloImpl implements Hello {@Overridepublic void sayHello() {System.out.println("Hello World");}}class MyInvocationHandler implements InvocationHandler {private Object target;public MyInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] throws Throwable {System.out.println("Invoking sayHello");Object result = method.invoke(target, args);return result;}}上面的JDK Proxy例子,非常簡單地實現了動態代理的構建和代理操作。首先,實現對應的InvocationHandler;然後,以接口Hello爲紐帶爲被調用目標構建代理對象,進而應用程序就可以使用代理對象間接行調用目標的邏輯,代理爲應用插入額外邏輯(這裏是println)提供便利的入口。從API設計和實現的角度,這種實現仍然有侷限性,因爲它是以接口中心的,相當於添加了一種對於被調用者沒有太大意義的限制。我們例化的是Proxy對象,而不是真正的被調用類型,這在實踐中還是可帶來各種不便和能力退化如果被調用者沒有實現接口,而我們還是希望利用動態代理機制,那可以考慮其他方式。我們知道Spring AOP支持兩種模式的動態代理,JDK Proxy或者cglib,如果我們選擇cglib方式,你會發現對接口的依被克服了。cglib動態代理採取的是創建目標類的子類的方式,因爲是子類化,我可以達到近似使用被調用者本身的效果。在Spring編程中,框架通常處理這種情況,當然我們也可以顯式指定。關於類似方案的實現細節我就不再詳細討論了。那我們在開發中怎樣選擇呢?我來簡單對比下兩種方式各自優勢。JDK Proxy的優勢:最小化依賴關係,減少依賴意味着簡化開發和維護,JDK本身的持,可能比cglib更加可靠。平滑進行JDK版本升級,而字節碼類庫通常需要進行更新以保證新版Java上能夠使用代碼實現簡單。基於類似cglib框架的優勢有的時候調用目標可能不便實現額外接口,從某種角度看,限定用者實現接口是有些侵入性的實踐,類似cglib動態代理就沒有這限制。只操作我們關心的類,而不必爲其他相關類增加工作量。高性能。另外,從性能角度,我想補充幾句。記得有人曾經得出結論說JDKProxy比cglib或者Javassist慢幾十倍。坦白說,不去爭論具體的benchmark細節,在主流JDK版本中,JDK Proxy在典型場景可以提供等的性能水平,數量級的差距基本上不是廣泛存在的。而且,反射機性能在現代JDK中,自身已經得到了極大的改進和優化,同時,JDK多功能也不完全是反射,同樣使用了ASM進行字節碼操作。我們在選型中,性能未必是唯一考量,可靠性、可維護性、編程工作等往往是更主要的考慮因素,畢竟標準類庫和反射編程的門檻要低得多,代碼量也是更加可控的,如果我們比較下不同開源項目在動態代開發上的投入,也能看到這一點。動態代理應用非常廣泛,雖然最初多是因爲RPC等使用進入我們視線但是動態代理的使用場景遠遠不僅如此,它完美符合Spring AOP等切編程。我在後面的專欄還會進一步詳細分析AOP的目的和能力。簡單說它可以看作是對OOP的一個補充,因爲OOP對於跨越不同對象或類分散、糾纏邏輯表現力不夠,比如在不同模塊的特定階段做一些事情類似日誌、用戶鑑權、全局性異常處理、性能監控,甚至事務處理等你可以參考下面這張圖AOP通過(動態)代理機制可以讓開發者從這些繁瑣事項中抽身出來大幅度提高了代碼的抽象程度和複用度。從邏輯上來說,我們在軟件計和實現中的類似代理,如Facade、Observer等很多設計目的,都可通過動態代理優雅地

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章