Byte Buddy是一個代碼生成和操縱庫,用於在Java應用程序的運行時創建和修改Java類,而無需編譯器。除了與Java類庫一起運送的代碼生成實用程序外,字節夥伴允許創建任意類,並且不限於實現創建運行時代理的接口。此外,Byte Buddy提供了方便的API,用於使用Java代理或構建過程中手動更改課程。
為了使用字節好友,不需要了解Java字節代碼或類文件格式。相比之下,Byte Buddy的API的目標是簡潔且易於理解的代碼。但是,字節好友仍然可以完全自定義,以定義定義自定義字節代碼的可能性。此外,API被設計為盡可能侵蝕性,因此,字節好友不會在其創建的類中留下任何痕跡。因此,生成的類可以存在,而無需在類路徑上需要字節好友。由於此功能,Byte Buddy的吉祥物被選為幽靈。
Byte Buddy用Java 5編寫,但支持任何Java版本的類別。字節好友是一個輕量級的庫,僅取決於Java字節代碼解析器庫ASM的訪問者API ASM本身不需要任何進一步的依賴。
乍一看,運行時代碼生成似乎是某種應該避免的黑魔法,只有少數開發人員編寫在運行時明確生成代碼的應用程序。但是,在創建需要與編譯時未知的任意代碼和類型交互的庫時,此圖片會發生變化。在這種情況下,庫實施程序通常必須在要求用戶實現庫專有界面或在用戶類型首先知道庫時在運行時生成代碼之間進行選擇。許多已知的庫,例如Spring或Hibernate選擇後一種方法,該方法在使用普通的舊Java對象的術語下在用戶中流行。結果,代碼生成已成為Java空間中普遍存在的概念。 Byte Buddy試圖創新Java類型的運行時創建,以便為依靠代碼生成的人提供更好的工具。
2015年10月,Byte Buddy獲得了Oracle的Duke選擇獎。該獎項讚賞Byte Buddy的“ Java Technology中的大量創新”。我們因獲得這個獎項而感到非常榮幸,並希望感謝所有幫助Byte Buddy取得成功的用戶和其他所有用戶。我們真的很感激!
Byte Buddy在生產質量方面提供了出色的性能。它是穩定的,在傑出的框架和工具(例如Mockito,Hibernate,Jackson,Google的Bazel Build System等)中使用。大量商業產品還使用了字節好友。目前每年下載超過7500萬次。
用字節好友說“你好”是盡可能容易的。 Java類的任何創建都以ByteBuddy
類的實例開頭,該類別代表創建新類型的配置:
Class <?> dynamicType = new ByteBuddy ()
. subclass ( Object . class )
. method ( ElementMatchers . named ( "toString" ))
. intercept ( FixedValue . value ( "Hello World!" ))
. make ()
. load ( getClass (). getClassLoader ())
. getLoaded ();
assertThat ( dynamicType . newInstance (). toString (), is ( "Hello World!" ));
上面示例中使用的默認ByteBuddy
配置在“類文件”格式的最新版本中創建了Java類,該類別由處理Java虛擬機理解。希望從示例代碼中可以明顯看出,創建類型將擴展Object
類並覆蓋其toString
方法,該方法應該返回Hello World!
。要覆蓋的方法由所謂的ElementMatcher
確定。在上面的示例中,使用了named(String)
預定義元素匹配器,該匹配器通過其確切名稱標識方法。字節好友配備了許多預定義且經過良好測試的匹配器,這些匹配器是在ElementMatchers
類中收集的,並且很容易組成。但是,自定義匹配器的創建與實現(功能) ElementMatcher
接口一樣簡單。
為了實現toString
方法, FixedValue
類定義了被覆蓋方法的恆定返回值。定義恆定值只是許多與字節好友一起運送的方法攔截器的一個示例。通過實現Implementation
接口,甚至可以通過自定義字節代碼來定義一種方法。
最後,創建了所述的Java類,然後加載到Java虛擬機中。為此,需要一個目標類加載程序。最終,我們可以通過在創建類的實例上調用toString
方法來說服自己,並找到返回值以表示我們期望的常數值。
當然,一個Hello World示例是一個過於簡單的用例,用於評估代碼生成庫的質量。實際上,這樣一個庫的用戶希望執行更複雜的操作,例如,將鉤子引入Java程序的執行路徑中。使用字節好友,但是這樣做同樣簡單。下面的示例可以探索如何攔截方法調用。
Byte Buddy通過Implementation
接口的實例表達動態定義的方法實現。在上一個示例中,已經證明了實現該接口的FixedValue
。通過實現此接口,字節好友的用戶可以轉到一種方法的定義自定義字節代碼的長度。通常,使用字節夥伴的預定義實現(例如MethodDelegation
更容易,該實現允許在Pline Java中實現任何方法。使用此實現是直接的,因為它通過將控制流委託給任何POJO來運行。作為這樣的pojo的一個例子,字節好友可以例如將呼叫重定向到以下類的唯一方法:
public class GreetingInterceptor {
public Object greet ( Object argument ) {
return "Hello from " + argument ;
}
}
請注意,上面的GreetingInterceptor
不取決於任何字節好友類型。這是個好消息,因為字節好友在課堂路徑上都不會生成Byte Buddy的課程!鑑於上述GreetingInterceptor
,我們可以使用字節好友實現Java 8 java.util.function.Function
界面及其摘要apply
方法:
Class <? extends java . util . function . Function > dynamicType = new ByteBuddy ()
. subclass ( java . util . function . Function . class )
. method ( ElementMatchers . named ( "apply" ))
. intercept ( MethodDelegation . to ( new GreetingInterceptor ()))
. make ()
. load ( getClass (). getClassLoader ())
. getLoaded ();
assertThat (( String ) dynamicType . newInstance (). apply ( "Byte Buddy" ), is ( "Hello from Byte Buddy" ));
執行上述代碼,字節夥伴實現了Java的Function
接口,並將apply
方法作為委託給我們以前定義的GreetingInterceptor
Pojo的實例。現在,每次調用Function::apply
方法時,將將控制流派遣到GreetingInterceptor::greet
,而後者方法的返回值將從接口的方法返回。
可以通過註釋Interceptor的參數來定義攔截器使用更多通用輸入和輸出。當字節好友發現註釋時,庫注入了攔截器參數所需的依賴性。更一般的攔截器的一個示例是以下類:
public class GeneralInterceptor {
@ RuntimeType
public Object intercept ( @ AllArguments Object [] allArguments ,
@ Origin Method method ) {
// intercept any method of any signature
}
}
使用上述攔截器,可以匹配和處理任何攔截方法。例如,當匹配Function::apply
時,該方法的參數將作為數組的單個元素傳遞。同樣,由於@Origin
註釋,將通過攔截器的第二個參數將通過Fuction::apply
的Method
。通過在該方法上聲明@RuntimeType
註釋,字節好友最終將返回的值施放為截距方法的返回值,如果有必要的話。在此過程中,字節好友還採用自動拳擊和拆箱。
除了已經提到的註釋外,還有許多其他預定義的註釋。例如,當在Runnable
或Callable
類型上使用@SuperCall
註釋時,字節好友注入代理實例,如果存在這種方法,該實例允許調用非吸收超級方法。即使字節好友不涵蓋用例,字節好友也提供了定義自定義註釋的擴展機制。
您可能希望使用這些註釋將您的代碼與字節好友聯繫起來。但是,Java會忽略註釋,以防類加載程序不可見。這樣,如果沒有字節好友,生成的代碼仍然可以存在!您可以在其Javadoc和Byte Buddy的教程中找到有關MethodDelegation
的更多信息以及其所有預定義註釋。
字節好友不僅限於創建子類,還可以重新定義現有代碼。為此,Byte Buddy為定義所謂的Java代理提供了方便的API。 Java代理是普通的舊Java程序,可用於在其運行時更改現有Java應用程序的代碼。例如,我們可以使用字節好友更改方法來打印其執行時間。為此,我們首先定義一個類似於以前示例的攔截器的攔截器:
public class TimingInterceptor {
@ RuntimeType
public static Object intercept ( @ Origin Method method ,
@ SuperCall Callable <?> callable ) {
long start = System . currentTimeMillis ();
try {
return callable . call ();
} finally {
System . out . println ( method + " took " + ( System . currentTimeMillis () - start ));
}
}
}
使用Java代理,我們現在可以將此攔截器應用於匹配ElementMatcher
的所有類型中的TypeDescription
。例如,我們選擇將上述攔截器添加到所有類型的名稱中,該名稱以Timed
結束。這樣做是為了簡單起見,而註釋可能是為生產代理標記此類類別的更合適的選擇。使用Byte Buddy的AgentBuilder
API,創建Java代理就像定義以下代理類一樣容易:
public class TimerAgent {
public static void premain ( String arguments ,
Instrumentation instrumentation ) {
new AgentBuilder . Default ()
. type ( ElementMatchers . nameEndsWith ( "Timed" ))
. transform (( builder , type , classLoader , module , protectionDomain ) ->
builder . method ( ElementMatchers . any ())
. intercept ( MethodDelegation . to ( TimingInterceptor . class ))
). installOn ( instrumentation );
}
}
與Java的main
方法類似, premain
方法是我們使用重新定義的任何Java代理的入口點。作為一個參數,Java代理接收了Instrumentation
接口的實例,該實例允許字節夥伴掛鉤JVM的標準API以進行運行時類重新定義。
該程序與一個清單文件一起包裝,其中指向TimerAgent
Premain-Class
屬性。現在,可以通過設置-javaagent:timingagent.jar
類似於將JAR添加到類路徑,可以將結果的JAR文件添加到任何Java應用程序中。隨著代理活動,所有Timed
結束的課程現在都將其執行時間打印到控制台。
字節好友還能夠通過禁用類文件格式並使用Advice
儀器來應用所謂的運行時附件。有關更多信息,請參閱Advice
和代理商班級課程的Javadoc和AgentBuilder
類。 Byte Buddy還通過ByteBuddy
實例或使用字節好友Maven和Gradle插件來顯式更改Java類。
Byte Buddy是一個綜合的庫,我們只刮擦了字節好友的能力表面。但是,Byte Buddy的目標是通過提供用於創建類的領域的語言來易於使用。大多數運行時代碼生成都可以通過編寫可讀代碼來完成,而無需了解Java的類文件格式。如果您想了解有關字節好友的更多信息,可以在字節好友的網頁上找到這樣的教程(也有中文翻譯)。
此外,Byte Buddy附帶了詳細的編碼文檔和廣泛的測試案例覆蓋範圍,也可以用作示例代碼。最後,您可以在Wiki中找到有關字節好友的最新文章和演示列表。使用字節好友時,還必須確保閱讀有關維護項目依賴性的以下信息。
字節好友的使用是免費的,不需要購買許可證。為了充分利用圖書館或獲得簡單的開始,可以購買培訓,開發時間或支持計劃。費率取決於參與的範圍和持續時間。請與[email protected]聯繫以獲取更多信息。
Byte Buddy在Tidelift上列出。如果您不使用字節好友在某種程度上要購買明確的支持並希望一般支持開源社區,請考慮訂閱。
您可以通過GitHub贊助商來支持我的工作。請注意,此選項僅適用於正在尋找簡單支付渠道並且不希望支持的商業參與者。通過GITHUB贊助商提供支持是不可能維持增值稅的合規性。請取消直接支持協議。
可以在堆棧溢出或字節好友郵件列表上提出一般問題,該郵件也可以作為問題的檔案。當然,錯誤報告也將在商業計劃之外考慮。對於開源項目,有時有可能會獲得擴展的幫助,以使Byte Buddy進入使用。
Byte Buddy寫在ASM的頂部,ASM是一個成熟且經過充分測試的圖書館,用於閱讀和寫作編譯的Java課程。為了允許高級類型的操作,字節好友有意將ASM API展示給其用戶。當然,直接使用ASM仍然是完全可選的,大多數用戶很可能永遠不會需要它。做出了這樣的選擇,使Byte Buddy的用戶不限制在其高級功能上,而是可以在必要時大驚小怪地實現自定義實現。
ASM先前已更改其公共API,但從圖書館的版本4開始添加了API兼容性的機制。為了避免版本與此類舊版本發生衝突,字節好友將ASM依賴性重新包裝到其自己的名稱空間中。如果您想直接使用ASM,則byte-buddy-dep
偽像提供了一個字節夥伴的版本,具有明確的依賴性ASM。這樣做時,您必須將字節好友和ASM重新包裝到您的命名空間中,以避免版本衝突。
請注意此項目的安全政策。
Byte Buddy支持第五版和單個JAR中的所有JVM版本的執行。這樣做是為了減輕通常需要支持未積極更新的舊應用程序或未知應用程序的Java代理的開發。為此,同時還支持現代Java,以及使用堆棧地圖框架(Byte Buddy Ship的主要罐子)作為多用釋放罐子的主要罐子,這些罐子中包含第五版和八版中的類文件。結果,字節好友的罐子尺寸較高。 JAR文件大小通常不是問題,因為大多數字節好友的類都不會加載。但是,在分發Java代理時,文件大小可能是一個問題。因此,由於已經需要將代理捆綁為一個JAR,因此建議刪除基本的Java五版本,或者要么多釋放的Java Java八版本的包含類文件,以減少此問題。為此,大多數構建插件都支持這一點,例如Maven Shade插件。
Byte Buddy已獲得自由和業務友好的Apache許可證的許可,版本2.0 ,可在Github上免費獲得。此外,根據3條規定BSD許可發布的字節捆綁分銷捆綁包ASM。
Byte Buddy二進製文件出版給Maven Central和Jcenter的存儲庫。從字節夥伴1.10.3開始,可以針對此PGP公鑰進行驗證工件簽名。可以根據該較舊的和較弱的證書來驗證較舊的版本。
該項目是使用Maven構建的。從您的外殼中,克隆和構建該項目將是這樣的:
git clone https://github.com/raphw/byte-buddy.git
cd byte-buddy
mvn package
在這些命令上,字節好友從github克隆並在您的機器上構建。 Root POM文件中列出了進一步的構建選項。可以使用至少6版6版的任何JDK構建字節好友。但是,建議使用至少版本8的JDK作為6和7版本的構建,需要使用未加密的HTTP。它的支持僅用於針對此JDK版本進行測試,並且可以使您接觸到中間的攻擊。因此,應避免這些構建。當前,Byte Buddy已在CI服務器上測試了JDK的6個及以上的版本。
請使用GitHub的問題跟踪器報告錯誤。進行代碼時,請提供證明您功能功能或證明錯誤修復的測試用例。此外,請確保您不會打破任何現有的測試用例。如果可能的話,請花點時間寫一些文檔。對於功能請求或一般反饋,您也可以使用問題跟踪器或在我們的郵件列表中與我們聯繫。
由於一排專門的資源和對項目的關注,因此也可以進行字節好友的工作。請花點時間看看這些支持者及其產品。