天天看點

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

程式設計語言通常有各種不同的分類角度,動态類型和靜态類型就是其中種分類角度,簡單區分就是語言類型資訊是在運作時檢查,還是編譯檢查。與其近似的還有一個對比,就是所謂強類型和弱類型,就是不同類型量指派時,是否需要顯式地(強制)進行類型轉換。那麼,如何分類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等很多設計目的,都可通過動态代理優雅地