編程語言有很多分類角度
動態(tài)類型和靜態(tài)類型是一種分類角度豁状,簡單區(qū)別就是語言類型信息是再運行時檢查,還是在編譯器檢查倒得。
強類型和弱類型也是一種分類角度泻红,簡單區(qū)別是不同類型變量賦值時,是否需要顯式地(強制)進行類型轉換霞掺。
所以谊路,通常認為 Java 是靜態(tài)強類型語言,但是因為其提供了類似反射等機制菩彬,也具備了部分動態(tài)語言的能力缠劝。
談談 Java 的反射機制,動態(tài)代理是基于什么原理骗灶?
典型回答
典型回答就是飄飄的將 Java 反射機制的介紹惨恭,功能。講動態(tài)代理的介紹耙旦,點幾個實現方法的名字脱羡,如 JDK 自身提供的動態(tài)代理、ASM免都、cglib锉罐、javassist。
考點分析
這個題目其實是有誘導的嫌疑绕娘,可能會下意識認為動態(tài)代理就是基于反射機制實現的脓规,這樣說雖然不錯但是就不全面。
功能才是目的险领,實現的方法有很多抖拦。
總的來說升酣,這個題目考察的是 Java 語言的另外一種基礎機制:反射,它就像一種魔法态罪,引入運行時自省的能力,賦予 Java 語言令人意外的活力下面,通過運行時操作元數據或對象复颈,Java 可以靈活地操作運行時才能確定的信息。而動態(tài)代理沥割,則是延伸出來的一種廣泛應用于產品開發(fā)中的技術耗啦,很多頻繁的重復編程,都可以被動態(tài)代理優(yōu)雅地解決机杜。
這道題有很多擴展或深挖的角度帜讲,比如
- 考察對反射機制地了解和掌握程度。
- 動態(tài)代理解決了什么問題椒拗,在業(yè)務系統(tǒng)中的應用場景是什么似将?
- JDK 動態(tài)代理在設計和實現上與 cglib 等方式有什么不同,進而該如何取舍蚀苛?
知識擴展
- 反射機制及其演進
Java 的反射機制在验,工具就是 java.lang.reflect 包下的各個類了,Class堵未、Field腋舌、Method、Constructor 等渗蟹,這些就是我們用來操作類和對象這些元數據的工具了块饺。
在 oracle 的 Javase 文檔中,簡單地講了幾個反射的基本用處和缺點
用處
- 擴展功能雌芽,給類賦予更多能力授艰。
- IDE 利用反射獲取類的成員信息,方便程序員
- 利用反射來 debug 和測試膘怕。比如開放私有方法想诅,進行測試,提高測試覆蓋率岛心。
缺點
- 反射是動態(tài)執(zhí)行的来破,因此 JVM 不能對它進行優(yōu)化。在頻繁執(zhí)行的代碼忘古,應該避免使用反射徘禁。
- 在安全管理下的環(huán)境,反射可能不被允許執(zhí)行髓堪。比如 Applet送朱。
- 因為反射暴露了類內部的信息娘荡。所以可能導致一些奇怪的邊緣影響,讓代碼功能混亂驶沼,輕便性被破壞炮沐。
關于反射,有一點值得特意提一下回怜,就是反射提供的 AccessbleObject.setAccessible(boolean flag)大年。它的子類也大多重寫了這個方法,這里的 setAccessible 讓我們可以在運行時修改成員的訪問限制玉雾,他有非常多的應用場景翔试,遍及我們日常的開發(fā)、測試复旬、依賴注入等各種框架中垦缅。比如在 O/R Mapping 框架中,我們?yōu)橐粋€ Java bean 運行時自動生成 getter驹碍、setter 的邏輯(注意這里是邏輯壁涎,不是方法),這是加載或者持久化數據必須的功能幸冻,框架通常就是利用反射做這個功能粹庞,不用開發(fā)者手動去學類似的重復代碼。
另一個典型的應用場景就是繞過 API 的訪問控制洽损。我們日常開發(fā)可能要被迫調用內部 API 去做些事情庞溜,比如自定義的高性能 NIO 框架需要顯式地釋放 DirectBuffer,使用反射繞開限制是一種辦法碑定。
在 Java 9 以后流码,Jigsaw 項目新增模塊化系統(tǒng),出于強封裝性的考慮延刘,對反射進行了限制漫试。Jigsaw 引入了所謂的 Open 的概念,如果反射操作的模塊和制定的包對反射調用者沒有 Open碘赖,那么 setAccessible 操作是被認為不合法的驾荣。
- 動態(tài)代理
動態(tài)代理,首先它是一個代理普泡。
而代理播掷,可以看成是對目標對象的一個包裝『嘲啵可以包裝 RPC 調用歧匈、限制對目標對象的訪問、擴展功能砰嘁、隱藏框架內部的尋址件炉、序列化勘究、反序列化的過程等。對調用者無需關心的內容斟冕,通過代理口糕,可以屏蔽掉這些細節(jié)。
動態(tài)代理是在靜態(tài)代理的上發(fā)展過來的磕蛇。靜態(tài)代理走净,就是在編譯時就確定好代理類,而動態(tài)代理是在運行時動態(tài)生成代理類孤里。早期的 RMI 就是典型的靜態(tài)代理,需要手動生成 stub 等各種文件橘洞,相當麻煩捌袜。雖然改進后的 RMI 不再需要做這些工作,但也是相當古老的技術了炸枣,會被逐步淘汰虏等。
在 JDK 中,動態(tài)代理有多種實現方式适肠,各有千秋霍衫。
比如 JDK 自帶的 Proxy 動態(tài)代理,第三方庫 cglib等侯养。
JDK 自帶的動態(tài)代理敦跌,優(yōu)點是
- 最小化依賴關系,減少依賴逛揩,意味著更低的開發(fā)成本和更高的可維護性柠傍。JDK 自身的支持,比第三方庫更有保障辩稽。
- 代碼實現簡單惧笛。
- 平滑進行 JDK 版本升級,而字節(jié)碼類庫通常要進行更新以保證在新版 Java 上正常使用逞泄。
缺點就是患整,基于接口。被代理類必須實現接口喷众,實現的接口不能有相同名字和參數的方法等
而類似于 cglib 框架的優(yōu)勢
- 通過生成子類的方式各谚,避開了接口限制。Spring AOP 就是使用 cglib 來避開接口限制侮腹。當被代理類有實現接口時嘲碧,Spring AOP 選擇使用 JDK Proxy,否則父阻,使用 cglib愈涩。不過我們也可以指定只用 cglib望抽。
- 只操作我們關心的類,而不必為其他相關類增加工作量履婉。
- 高性能煤篙。
關于性能角度,可以補充幾句毁腿。有人曾經得出結論說 JDK Proxy 比 cglib 或者 javassist 慢幾十倍辑奈。坦白說,不去爭論具體的 benckmark 細節(jié),在主流的 JDK 版本中, JDK Proxy 在典型場景可以提供對等的性能水平桑谍,數量級的差距基本上不是廣泛存在的昂勉。而且,反射機制在現代 JDK 中,自身已經得到了極大的改進
缺點是,它是一個第三方類庫。我們在選型中臣嚣,性能未必是唯一的考量“疲可靠性硅则、可維護性、編程工作量等往往是更主要的考慮因素株婴,畢竟標準類庫和反射編程的門檻要低得多怎虫,代碼量也是更加可控的,如果我們比較下不同開源項目在動態(tài)代理上的投入督暂,也能看到這一點揪垄。
動態(tài)代理的應用非常廣泛,最然最初是因為 RPC 等使用進入我們的視線逻翁,但動態(tài)代理的使用場景遠遠不僅如此饥努。它完美符合 Spring AOP 等切面編程。通過動態(tài)代理八回,實現 AOP酷愧,補充了 OOP 對于跨越不同對象或者類的分散、糾纏邏輯表現力不夠的缺點缠诅。使得我們可以通過 AOP 在不同模塊特定階段做一些通用溶浴、指定的事情。類似日志管引、用戶鑒權士败、全局異常處理、性能監(jiān)控、甚至事務處理等谅将。
AOP 通過動態(tài)代理漾狼,讓開發(fā)者從這些繁瑣的事項中抽身出來,大幅度提高了代碼的抽象程度和復用度饥臂。
最近的一個項目逊躁,用虛幻連接實現一個線程池,就利用了動態(tài)代理包裝了底層真正的數據庫連接隅熙。