框架中建立浮动框架_建立代理,而不是框架
框架中建立浮動框架
自從引入Java注釋以來,它已成為大型應(yīng)用程序框架API的組成部分。 此類API的很好示例是Spring或Hibernate的示例,其中添加了幾行注釋代碼可實現(xiàn)非常復(fù)雜的程序邏輯。 盡管人們可以爭論這些特定API的缺點,但大多數(shù)開發(fā)人員都會同意,這種使用正確的聲明式編程形式具有很強的表現(xiàn)力。 但是,只有很少的開發(fā)人員選擇為其自己的框架或應(yīng)用程序中間件實現(xiàn)基于注釋的API,主要是因為它們難以實現(xiàn)。 在下一篇文章中,我想說服您,相比之下,此類API的實現(xiàn)非常簡單,并且使用正確的工具不需要任何有關(guān)Java內(nèi)在函數(shù)的專門知識。
實施基于注釋的API時,一個非常明顯的問題是,正在執(zhí)行的Java運行時未處理注釋。 結(jié)果,不可能為給定的用戶注釋分配特定的含義。 例如,考慮我們想要定義一個@Log批注,該批注我們希望提供它用于簡單地記錄每次被注釋方法的調(diào)用:
class Service {@Logvoid doSomething() { // do something ...} }由于@Log批注@Log其@Log就無法執(zhí)行程序邏輯,因此,由批注的用戶來執(zhí)行請求的日志記錄。 顯然,這使注釋幾乎無用,因為我們無法調(diào)用doSomething方法,并且期望在日志中觀察到相應(yīng)的語句。 到目前為止,注釋僅用作標(biāo)記,而沒有貢獻任何程序邏輯。
縮小差距
為了克服這種明顯的局限性,許多注釋驅(qū)動的框架將子類與方法重寫結(jié)合使用以實現(xiàn)與特定注釋關(guān)聯(lián)的邏輯。 這通常稱為子類檢測。 對于建議的@Log注釋,子類檢測將導(dǎo)致創(chuàng)建一個類似于以下LoggingService :
class LoggingService extends Service {@Overridevoid doSomething() { Logger.log("doSomething() was called");super.doSomething();} }當(dāng)然,上述類通常不需要顯式實現(xiàn)。 相反,一種流行的方法是僅在運行時使用諸如cglib或Javassist之類的代碼生成庫來生成此類。 這兩個庫均提供用于創(chuàng)建程序增強子類的簡單API。 作為將類的創(chuàng)建延遲到運行時的一個很好的副作用,建議的日志記錄框架無需任何特殊的準(zhǔn)備就可以使用,并且始終與用戶代碼保持同步。 如果將以更明確的方式創(chuàng)建類(例如通過在構(gòu)建過程中編寫Java源文件)來創(chuàng)建類,則情況并非如此。
但是,它可擴展嗎?
然而,該解決方案帶來了另一個缺點。 通過將注釋的邏輯放入生成的子類中,必須不再通過其構(gòu)造函數(shù)實例化示例Service類。 否則,仍將不記錄帶注釋的方法的調(diào)用:顯然,調(diào)用構(gòu)造函數(shù)不會創(chuàng)建所需子類的實例。 更糟糕的是,當(dāng)使用建議的運行時生成方法時,由于Java編譯器不知道運行時生成的類,因此LoggingService無法直接實例化。
因此,諸如Spring或Hibernate之類的框架使用對象工廠,并且不允許直接實例化被視為其框架邏輯一部分的對象。 使用Spring,由于工廠的所有對象都是已經(jīng)由框架首先創(chuàng)建的托管bean,因此自然可以通過工廠創(chuàng)建對象。 同樣,大多數(shù)Hibernate實體是作為查詢結(jié)果創(chuàng)建的,因此不會顯式實例化。 但是,例如當(dāng)保存尚未在數(shù)據(jù)庫中表示的實體實例時,Hibernate的用戶需要用存儲后從Hibernate返回的實例替換最近保存的實例。 通過查看有關(guān)Hibernate的問題,忽略這種替代已經(jīng)構(gòu)成了一個常見的初學(xué)者錯誤。 除此之外,由于有了這些工廠,子類??檢測對于框架用戶來說幾乎是透明的,因為Java的類型系統(tǒng)暗示子類可以替代其任何超類。 因此, LoggingService的實例可以在用戶期望用戶定義的Service類的實例的任何地方使用。
不幸的是,事實證明,這種批準(zhǔn)的實例工廠方法難以實現(xiàn)建議的@Log注釋,因為這將需要對可能被注釋的類的每個單個實例使用工廠。 顯然,這將增加大量的樣板代碼。 通過不將日志記錄指令硬編碼到方法中,我們甚至可能創(chuàng)建比我們避免的樣板更多的樣板。 同樣,意外使用構(gòu)造函數(shù)會給Java程序帶來細(xì)微的錯誤,因為此類實例上的注釋將不再像我們期望的那樣被對待。 另一個問題是,工廠不容易構(gòu)成。 如果我們想向已經(jīng)是Hibernate bean的類添加@Log注釋怎么辦? 這聽起來很瑣碎,但需要進行大量配置才能合并兩個框架的工廠。 最后,最終的工廠膨脹的代碼看起來不會太漂亮,以致于無法使用該框架進行遷移。 這是使用Java代理進行檢測的地方。 這種低估的檢測形式為討論的子類檢測提供了很好的選擇。
一個簡單的代理
Java代理由一個簡單的jar文件表示。 與普通Java程序類似,Java代理將某些類定義為入口點。 然后,期望此類定義一個靜態(tài)方法,該方法在調(diào)用實際Java程序的main方法之前將被調(diào)用:
class MyAgent {public static void premain(String args, Instrumentation inst) {// implement agent here ...} }處理Java代理時,最有趣的部分是premain方法的第二個參數(shù),它表示Instrumentation接口的實例。 通過定義ClassFileTransformer此接口提供了一種掛接到Java的類加載過程的方法。 使用此類轉(zhuǎn)換器,我們能夠在Java程序首次使用之前對其進行增強。
乍一看,使用此API聽起來似乎很直接,但它卻帶來了新的挑戰(zhàn)。 通過更改已編譯的Java類(表示為Java字節(jié)碼)來執(zhí)行類文件轉(zhuǎn)換。 實際上,Java虛擬機不知道編程語言是什么Java。 相反,它僅處理此字節(jié)碼。 還要感謝字節(jié)碼抽象,JVM能夠輕松運行其他語言,例如Scala或Groovy。 結(jié)果,注冊的類文件轉(zhuǎn)換器僅提供將給定的字節(jié)(代碼)數(shù)組轉(zhuǎn)換為另一個數(shù)組的功能。
即使諸如ASM或BCEL之類的庫提供了用于處理已編譯Java類的簡單API,也只有很少的開發(fā)人員具有處理原始字節(jié)碼的經(jīng)驗。 更糟糕的是,正確執(zhí)行字節(jié)碼操作通常很麻煩,甚至通過拋出令人討厭且不可恢復(fù)的VerifierError來彌補虛擬機的小錯誤。 幸運的是,有更好,更輕松的方式來處理字節(jié)碼。
我編寫和維護的Byte Buddy庫提供了一個簡單的API,用于處理已編譯的Java類和創(chuàng)建Java代理。 在某些方面,Byte Buddy是類似于cglib和Javassist的代碼生成庫。 但是,除了那些庫之外,Byte Buddy還提供了一個統(tǒng)一的API,用于實現(xiàn)子類和重新定義現(xiàn)有的類。 但是,對于本文,我們只希望研究使用Java代理重新定義類。 好奇的讀者可以參考Byte Buddy的網(wǎng)頁,該網(wǎng)頁提供了有關(guān)其完整功能集的詳細(xì)教程 。
使用Byte Buddy作為簡單代理
字節(jié)伙伴提供的一種定義工具的方法是使用依賴項注入。 這樣做,一個攔截器類(由任何普通的舊Java對象表示)都可以通過其參數(shù)的注釋簡單地請求任何必需的信息。 例如,通過在“ Method類型的參數(shù)上使用Byte Buddy的@Origin批注,Byte Buddy可以@Origin出攔截器想知道要攔截的方法。 這樣,我們可以定義一個通用攔截器,該攔截器始終知道要攔截的方法:
class LogInterceptor {static void log(@Origin Method method) {Logger.log(method + " was called");} }當(dāng)然,Byte Buddy附帶了更多注釋。
但是,此攔截器如何表示我們打算用于擬議的日志記錄框架的邏輯? 到目前為止,我們僅定義了一個記錄方法調(diào)用的攔截器。 我們錯過的是該方法的原始代碼的后續(xù)調(diào)用。 幸運的是,Byte Buddy的樂器是可以組合的。 首先,我們?yōu)樽罱x的LogInterceptor定義一個MethodDelegation ,默認(rèn)情況下,該方法在每次調(diào)用方法時都會調(diào)用攔截器的靜態(tài)方法。 從此開始,我們可以隨后以SuperMethodCall表示的原始方法代碼的后續(xù)調(diào)用來組成委托:
MethodDelegation.to(LogInterceptor.class).andThen(SuperMethodCall.INSTANCE)最后,我們需要告知Byte Buddy有關(guān)指定工具要攔截的方法的信息。 如前所述,我們希望該工具適用于任何使用@Log注釋的方法。 在字節(jié)伙伴中,可以使用類似于Java 8謂詞的ElementMatcher來標(biāo)識方法的這種屬性。 在靜態(tài)實用程序類ElementMatchers ,我們已經(jīng)可以找到合適的匹配器來標(biāo)識帶有給定注釋的方法: ElementMatchers.isAnnotatedWith(Log.class) 。
有了這些,我們現(xiàn)在可以定義一個實現(xiàn)建議的日志記錄框架的代理。 對于Java代理,Byte Buddy提供了一個實用程序API,該API建立在我們剛剛討論的類修改API的基礎(chǔ)上。 與后一種API相似,它被設(shè)計為領(lǐng)域特定的語言,因此僅通過查看實現(xiàn)即可輕松理解其含義。 如我們所見,定義這樣的代理僅需要幾行代碼:
class LogAgent {public static void premain(String args, Instrumentation inst) {new AgentBuilder.Default().rebase(ElementMatchers.any()).transform( builder -> return builder.method(ElementMatchers.isAnnotatedWith(Log.class)).intercept(MethodDelegation.to(LogInterceptor.class).andThen(SuperMethodCall.INSTANCE)) ).installOn(inst);} }請注意,這種最小的Java代理不會干擾應(yīng)用程序的其余部分,因為任何執(zhí)行代碼都會觀察所檢測的Java類,就像將日志記錄語句硬編碼到任何帶注釋的方法中一樣。
現(xiàn)實生活如何?
當(dāng)然,提出的基于代理的記錄器是一個簡單的例子。 通常情況下,開箱即用的框架提供了類似的功能,例如Spring或Dropwizard都是很棒的。 但是,對于如何解決編程問題,人們通常也對此類框架持意見。 對于大量的軟件應(yīng)用程序,這可能不是問題。 但是,有時這些意見會阻礙更大的發(fā)展。 然后,圍繞框架關(guān)于如何做事情的假設(shè)進行工作,不僅會導(dǎo)致一些問題,而且還會導(dǎo)致抽象的泄漏,并可能導(dǎo)致軟件維護成本激增。 尤其是當(dāng)應(yīng)用程序隨著時間增長和變化,并且其需求與基礎(chǔ)框架所提供的需求有所不同時,尤其如此。
相反,當(dāng)以圖片混合的方式組成更專業(yè)的框架或庫時,一個簡單地用另一個替換了有問題的組件。 如果這兩種方法都不起作用,甚至可以實施自定義解決方案,而不會干擾應(yīng)用程序的其余部分。 據(jù)我們了解,這似乎很難在JVM上實現(xiàn),這主要是Java嚴(yán)格類型系統(tǒng)的結(jié)果。 但是,使用Java代理很有可能克服這些類型限制。
我的觀點是,我認(rèn)為至少所有跨領(lǐng)域的關(guān)注都應(yīng)該由代理驅(qū)動的專用庫來解決,而不是由整體框架的內(nèi)置模塊來解決。 我真的希望更多的應(yīng)用程序會考慮這種方法。 在最瑣碎的情況下,使用代理在感興趣的方法上注冊偵聽器并將其從那里獲取就足夠了。 這種組成代碼模塊的間接方法避免了我在遇到的大部分Java應(yīng)用程序中觀察到的強大凝聚力。 作為一個很好的副作用,它也使測試非常容易。 與運行測試類似,在啟動應(yīng)用程序時不添加代理,可以有針對性地禁用某些應(yīng)用程序功能,例如日志記錄。 所有這些都無需更改代碼行,也不會使應(yīng)用程序崩潰,因為JVM只是忽略了它在運行時無法解析的注釋。 安全性,日志記錄,緩存,有許多原因應(yīng)以建議的方式處理這些主題,甚至更多。 因此,有時創(chuàng)建代理而不是框架。
翻譯自: https://www.javacodegeeks.com/2015/01/make-agents-not-frameworks.html
框架中建立浮動框架
總結(jié)
以上是生活随笔為你收集整理的框架中建立浮动框架_建立代理,而不是框架的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: vbs脚本病毒代码清理详细教程
- 下一篇: 电脑截屏设置(电脑截屏设置在哪里找)
