通過使用Byte?Buddy便捷創(chuàng)建Java?Agent
Java agent 是在另外一個 Java 應(yīng)用(“目標(biāo)”應(yīng)用)啟動之前要執(zhí)行的 Java 程序,這樣 agent 就有機會修改目標(biāo)應(yīng)用或者應(yīng)用所運行的環(huán)境。在本文中,我們將會從基礎(chǔ)內(nèi)容開始,逐漸增強其功能,借助字節(jié)碼操作工具 Byte Buddy,使其成為高級的 agent 實現(xiàn)。
在最基本的用例中,Java agent 會用來設(shè)置應(yīng)用屬性或者配置特定的環(huán)境狀態(tài),agent 能夠作為可重用和可插入的組件。如下的樣例描述了這樣的一個 agent,它設(shè)置了一個系統(tǒng)屬性,在實際的程序中就可以使用該屬性了:
public class Agent { public static void premain(String arg) { System.setProperty("my-property", “foo”); } }
如上面的代碼所述,Java agent 的定義與其他的 Java 程序類似,只不過它使用premain
方法替代 main 方法作為入口點。顧名思義,這個方法能夠在目標(biāo)應(yīng)用的 main 方法之前執(zhí)行。相對于其他的 Java 程序,編寫 agent 并沒有特定的規(guī)則。有一個很小的區(qū)別在于,Java agent 接受一個可選的參數(shù),而不是包含零個或更多參數(shù)的數(shù)組。
如果要使用這個 agent,必須要將 agent 類和資源打包到 jar 中,并且在 jar 的 manifest 中要將Agent-Class
屬性設(shè)置為包含premain
方法的 agent 類。(agent 必須要打包到 jar 文件中,它不能通過拆解的格式進行指定。)接下來,我們需要啟動應(yīng)用程序,并且在命令行中通過 javaagent 參數(shù)來引用 jar 文件的位置:
java -javaagent:myAgent.jar -jar myProgram.jar
我們還可以在位置路徑上設(shè)置可選的 agent 參數(shù)。在下面的命令中會啟動一個 Java 程序并且添加給定的 agent,將值 myOptions 作為參數(shù)提供給premain
方法:
java -javaagent:myAgent.jar=myOptions -jar myProgram.jar
通過重復(fù)使用javaagent
命令,能夠添加多個 agent。
但是,Java agent 的功能并不局限于修改應(yīng)用程序環(huán)境的狀態(tài),Java agent 能夠訪問 Java instrumentation API,這樣的話,agent 就能修改目標(biāo)應(yīng)用程序的代碼。Java 虛擬機中這個鮮為人知的特性提供了一個強大的工具,有助于實現(xiàn)面向切面的編程。
如果要對 Java 程序進行這種修改,我們需要在 agent 的premain
方法上添加類型為Instrumentation
的第二個參數(shù)。Instrumentation 參數(shù)可以用來執(zhí)行一系列的任務(wù),比如確定對象以字節(jié)為單位的精確大小以及通過注冊ClassFileTransformers
實際修改類的實現(xiàn)。ClassFileTransformers
注冊之后,當(dāng)類加載器(class loader)加載類的時候都會調(diào)用它。當(dāng)它被調(diào)用時,在類文件所代表的類加載之前,類文件 transformer 有機會改變或完全替換這個類文件。按照這種方式,在類使用之前,我們能夠增強或修改類的行為,如下面的樣例所示:
public class Agent { public static void premain(String argument, Instrumentation inst) { inst.addTransformer(new ClassFileTransformer() { @Override public byte[] transform( ClassLoader loader, String className, Class<?> classBeingRedefined, // 如果類之前沒有加載的話,值為 null ProtectionDomain protectionDomain, byte[] classFileBuffer) { // 返回改變后的類文件。 } }); } }
通過使用Instrumentation
實例注冊上述的ClassFileTransformer
之后,每個類加載的時候,都會調(diào)用這個 transformer。為了實現(xiàn)這一點,transformer 會接受一個二進制和類加載器的引用,分別代表了類文件以及試圖加載類的類加載器。
Java agent 也可以在 Java 應(yīng)用的運行期注冊,如果是在這種場景下,instrumentation API 允許重新定義已加載的類,這個特性被稱之為“HotSwap”。不過,重新定義類僅限于替換方法體。在重新定義類的時候,不能新增或移除類成員,并且類型和簽名也不能進行修改。當(dāng)類第一次加載的時候,并沒有這種限制,如果是在這樣的場景下,那classBeingRedefined
會被設(shè)置為 null。
Java 字節(jié)碼與類文件格式
類文件代表了 Java 類編譯之后的狀態(tài)。類文件中會包含字節(jié)碼,這些字節(jié)碼代表了 Java 源碼中最初的程序指令。Java 字節(jié)碼可以視為 Java 虛擬機的語言。實際上,JVM 并不會將 Java 視為編程語言,它只能處理字節(jié)碼。因為它采用二進制的表現(xiàn)形式,所以相對于程序的源碼,它占用的空間更少。除此之外,將程序以字節(jié)碼的形式進行表現(xiàn)能夠更容易地編譯 Java 以外的其他語言,如 Scala 或 Clojure,從而讓它們運行在 JVM 上。如果沒有字節(jié)碼作為中間語言的話,那么其他的程序在運行之前,可能還需要將其轉(zhuǎn)換為 Java 源碼。
但是,在代碼處理的時候,這種抽象卻帶來了一定的成本。如果要將ClassFileTransformer
應(yīng)用到某個類上,那我們不能將該類按照 Java 源碼的形式進行處理,甚至不能假設(shè)被轉(zhuǎn)換的代碼最初是由 Java 編寫而成的。更糟糕的是,探查類成員或注解的反射 API 也是禁止使用的,這是因為類加載之前,我們無法訪問這些 API,而在轉(zhuǎn)換進程完成之前,是無法進行加載的。
所幸的是,Java 字節(jié)碼相對來講是一個比較簡單的抽象形式,它包含了很少量的操作,稍微花點功夫我們就能大致將其掌握起來。Java 虛擬機執(zhí)行程序的時候,會以基于棧的方式來處理值。字節(jié)碼指令一般會告知虛擬機,需要從操作數(shù)棧(operand stack)上彈出值,執(zhí)行一些操作,然后再將結(jié)果壓到棧中。
讓我們考慮一個簡單的樣例:將數(shù)字 1 和 2 進行相加操作。JVM 首先會將這兩個數(shù)字壓到棧中,這是通過 _iconst_1_ 和 _iconst_2_ 這兩個字節(jié)指令實現(xiàn)的。_iconst_1_ 是個單字節(jié)的便捷運算符(operator),它會將數(shù)字 1 壓到棧中。與之類似,_iconst_2_ 會將數(shù)字 2 壓到棧中。然后,會執(zhí)行 _iadd_ 指令,它會將棧中最新的兩個值彈出,將它們求和計算的結(jié)果重新壓到棧中。在類文件中,每個指令并不是以其易于記憶的名稱進行存儲的,而是以一個字節(jié)的形式進行存儲,這個字節(jié)能夠唯一地標(biāo)記特定的指令,這也是 _bytecode_ 這個術(shù)語的來歷。上文所述的字節(jié)碼指令及其對操作數(shù)棧的影響,通過下面的圖片進行了可視化。
對于人類用戶來講,會更喜歡源碼而不是字節(jié)碼,不過幸運的是 Java 社區(qū)創(chuàng)建了多個庫,能夠解析類文件并將緊湊的字節(jié)碼暴露為具有名稱的指令流。例如,流行的 ASM 庫提供了一個簡單的 visitor API,它能夠?qū)㈩愇募饰鰹槌蓡T和方法指令,其操作方式類似于閱讀 XML 文件時的 SAX 解析器。如果使用 ASM 的話,那上述樣例中的字節(jié)碼可以按照如下的代碼來進行實現(xiàn)(在這里,ASM 方式的指令是visitIns
,能夠提供修正的方法實現(xiàn)):
MethodVisitor methodVisitor = ... methodVisitor.visitIns(Opcodes.ICONST_1); methodVisitor.visitIns(Opcodes.ICONST_2); methodVisitor.visitIns(Opcodes.IADD);
需要注意的是,字節(jié)碼規(guī)范只不過是一種比喻的說法(metaphor),因為 Java 虛擬機允許將程序轉(zhuǎn)換為優(yōu)化后的機器碼(machine code),只要程序的輸出能夠保證是正確的即可。因為字節(jié)碼的簡潔性,所以在已有的類中取代和修改指令是很簡單直接的。因此,使用 ASM 及其底層的 Java 字節(jié)碼基礎(chǔ)就足以實現(xiàn)類轉(zhuǎn)換的 Java agent,這需要注冊一個ClassFileTransformer
,它會使用這個庫來處理其參數(shù)。
克服字節(jié)碼的不足
對于實際的應(yīng)用來講,解析原始的類文件依然意味著有很多的手動工作。Java 程序員通常感興趣的是類型層級結(jié)構(gòu)中的類。例如,某個 Java agent 可能需要修改所有實現(xiàn)給定接口的類。如果要確定某個類的超類,那只靠解析ClassFileTransformer
所給定的類文件就不夠了,類文件中只包含了直接超類和接口的名字。為了解析可能的超類型關(guān)聯(lián)關(guān)系,程序員依然需要定位這些類型的類文件。
在項目中直接使用 ASM 的另外一個困難在于,團隊中需要有開發(fā)人員學(xué)習(xí) Java 字節(jié)碼的基礎(chǔ)知識。在實踐中,這往往會導(dǎo)致很多的開發(fā)人員不敢再去修改字節(jié)碼操作相關(guān)的代碼。如果這樣的話,實現(xiàn) Java agent 很容易為項目的長期維護帶來風(fēng)險。
為了克服這些問題,我們最好使用較高層級的抽象來實現(xiàn) Java agent,而不是直接操作 Java 字節(jié)碼。Byte Buddy 是開源的、基于 Apache 2.0 許可證的庫,它致力于解決字節(jié)碼操作和 instrumentation API 的復(fù)雜性。Byte Buddy 所聲稱的目標(biāo)是將顯式的字節(jié)碼操作隱藏在一個類型安全的領(lǐng)域特定語言背后。通過使用 Byte Buddy,任何熟悉 Java 編程語言的人都有望非常容易地進行字節(jié)碼操作。
Byte Buddy 簡介
Byte Buddy 的目的并不僅僅是為了生成 Java agent。它提供了一個 API 用于生成任意的 Java 類,基于這個生成類的 API,Byte Buddy 提供了額外的 API 來生成 Java agent。
作為 Byte Buddy 的簡介,如下的樣例展現(xiàn)了如何生成一個簡單的類,這個類是 Object 的子類,并且重寫了 toString 方法,用來返回“Hello World!”。與原始的 ASM 類似,“intercept”會告訴 Byte Buddy 為攔截到的指令提供方法實現(xiàn):
Class<?> dynamicType = new ByteBuddy() .subclass(Object.class) .method(ElementMatchers.named("toString")) .intercept(FixedValue.value("Hello World!")) .make() .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) .getLoaded();
從上面的代碼中,我們可以看到 Byte Buddy 要實現(xiàn)一個方法分為兩步。首先,編程人員需要指定一個ElementMatcher
,它負(fù)責(zé)識別一個或多個需要實現(xiàn)的方法。Byte Buddy 提供了功能豐富的預(yù)定義攔截器(interceptor),它們暴露在ElementMatchers
類中。在上述的例子中,toString
方法完全精確匹配了名稱,但是,我們也可以匹配更為復(fù)雜的代碼結(jié)構(gòu),如類型或注解。
當(dāng) Byte Buddy 生成類的時候,它會分析所生成類型的類層級結(jié)構(gòu)。在上述的例子中,Byte Buddy 能夠確定所生成的類要繼承其超類 Object 的名為 toString 的方法,指定的匹配器會要求 Byte Buddy 重寫該方法,這是通過隨后的
<ahref="http://bytebuddy.net/javadoc/0.7.1/net/bytebuddy/implementation/Implementation.html">Implementation</a>
實例實現(xiàn)的,在我們的樣例中,這個實例也就是
<ahref="http://bytebuddy.net/javadoc/0.7.1/net/bytebuddy/implementation/FixedValue.html">FixedValue</a>
當(dāng)創(chuàng)建子類的時候,Byte Buddy 始終會攔截(intercept)
一個匹配的方法,在生成的類中重寫該方法。但是,我們在本文稍后將會看到 Byte Buddy 還能夠重新定義已有的類,而不必通過子類的方式來實現(xiàn)。在這種情況下,Byte Buddy 會將已有的代碼替換為生成的代碼,而將原有的代碼復(fù)制到另外一個合成的(synthetic)方法中。
在我們上面的代碼樣例中,匹配的方法進行了重寫,在實現(xiàn)里面,返回了固定的值“Hello World!”。intercept
方法接受 Implementation 類型的參數(shù),Byte Buddy 自帶了多個預(yù)先定義的實現(xiàn),如上文所使用的FixedValue
類。但是,如果需要的話,可以使用前文所述的 ASM API 將某個方法實現(xiàn)為自定義的字節(jié)碼,Byte Buddy 本身也是基于 ASM API 實現(xiàn)的。
定義完類的屬性之后,就能通過 make 方法來進行生成。在樣例應(yīng)用中,因為用戶沒有指定類名,所以生成的類會給定一個任意的名稱。最終,生成的類將會使用ClassLoadingStrategy
來進行加載。通過使用上述的默認(rèn)WRAPPER
策略,類將會使用一個新的類加載器進行加載,這個類加載器會使用環(huán)境類加載器作為父加載器。
類加載之后,使用 Java 反射 API 就可以訪問它了。如果沒有指定其他構(gòu)造器的話,Byte Buddy 將會生成類似于父類的構(gòu)造器,因此生成的類可以使用默認(rèn)的構(gòu)造器。這樣,我們就可以檢驗生成的類重寫了toString
方法,如下面的代碼所示:
assertThat(dynamicType.newInstance().toString(), is("Hello World!"));
當(dāng)然,這個生成的類并沒有太大的用處。對于實際的應(yīng)用來講,大多數(shù)方法的返回值是在運行時計算的,這個計算過程要依賴于方法的參數(shù)和對象的狀態(tài)。
通過委托實現(xiàn) Instrumentation
要實現(xiàn)某個方法,有一種更為靈活的方式,那就是使用 Byte Buddy 的 MethodDelegation。通過使用方法委托,在生成重寫的實現(xiàn)時,我們就有可能調(diào)用給定類和實例的其他方法。按照這種方式,我們可以使用如下的委托器(delegator)重新編寫上述的樣例:
class ToStringInterceptor { static String intercept() { return “Hello World!”; } }
借助上面的 POJO 攔截器,我們就可以將之前的 FixedValue 實現(xiàn)替換為
MethodDelegation.to(ToStringInterceptor.class):
Class<?> dynamicType = new ByteBuddy() .subclass(Object.class) .method(ElementMatchers.named("toString")) .intercept(MethodDelegation.to(ToStringInterceptor.class)) .make() .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) .getLoaded();
使用上述的委托器,Byte Buddy 會在 to 方法所給定的攔截目標(biāo)中,確定 _ 最優(yōu)的調(diào)用方法 _。就ToStringInterceptor.class
來講,選擇過程只是非常簡單地解析這個類型的唯一靜態(tài)方法而已。在本例中,只會考慮一個靜態(tài)方法,因為委托的目標(biāo)中指定的是一個 _ 類 _。與之不同的是,我們還可以將其委托給某個類的 _ 實例 _,如果是這樣的話,Byte Buddy 將會考慮所有的虛方法(virtual method)。如果類或?qū)嵗嫌卸鄠€這樣的方法,那么 Byte Buddy 首先會排除掉所有與指定 instrumentation 不兼容的方法。在剩余的方法中,庫將會選擇最佳的匹配者,通常來講這會是參數(shù)最多的方法。我們還可以顯式地指定目標(biāo)方法,這需要縮小合法方法的范圍,將ElementMatcher
傳遞到MethodDelegation
中,就會進行方法的過濾。例如,通過添加如下的filter
,Byte Buddy 只會將名為“intercept”的方法視為委托目標(biāo):
MethodDelegation.to(ToStringInterceptor.class) .filter(ElementMatchers.named(“intercept”))
執(zhí)行上面的攔截之后,被攔截到的方法依然會打印出“Hello World!”,但是這次的結(jié)果是動態(tài)計算的,這樣的話,我們就可以在攔截器方法上設(shè)置斷點,所生成的類每次調(diào)用toString
時,都會觸發(fā)攔截器的方法。
當(dāng)我們?yōu)閿r截器方法設(shè)置參數(shù)時,就能釋放出MethodDelegation
的全部威力。這里的參數(shù)通常是帶有注解的,用來要求 Byte Buddy 在調(diào)用攔截器方法時,注入某個特定的值。例如,通過使用@Origin
注解,Byte Buddy 提供了添加 instrument 功能的方法的實例,將其作為 Java 反射 API 中類的實例:
class ContextualToStringInterceptor { static String intercept(@Origin Method m) { return “Hello World from ” + m.getName() + “!”; } }
當(dāng)攔截toString
方法時,對 instrument 方法的調(diào)用將會返回“Hello world from toString!”。
除了@Origin
注解以外,Byte Buddy 提供了一組功能豐富的注解。例如,通過在類型為 Callable
的參數(shù)上使用@Super
注解,Byte Buddy 會創(chuàng)建并注入一個代理實例,它能夠調(diào)用被 instrument 方法的原始代碼。如果對于特定的用戶場景,所提供的注解不能滿足需求或者不太適合的話,我們甚至能夠注冊自定義的注解,讓這些注解注入用戶特定的值。
實現(xiàn)方法級別的安全性
可以看到,我們在運行時可以借助簡單的 Java 代碼,使用 MethodDelegation 來動態(tài)重寫某個方法。這只是一個簡單的樣例,但是這項技術(shù)可以用到更加實際的應(yīng)用之中。在本文剩余的內(nèi)容中,我們將會開發(fā)一個樣例,它會使用代碼生成技術(shù)實現(xiàn)一個注解驅(qū)動的庫,用來限制方法級別的安全性。在我們的第一個迭代中,這個庫會通過生成子類的方式來限制安全性。然后,我們將會采取相同的方式來實現(xiàn) Java agent,完成相同的功能。
樣例庫會使用如下的注解,允許用戶指定某個方法需要考慮安全因素:
@interface Secured { String user(); }
例如,假設(shè)應(yīng)用需要使用如下的Service
類來執(zhí)行敏感操作,并且只有用戶被認(rèn)證為管理員才能執(zhí)行該方法。這是通過為執(zhí)行這個操作的方法聲明 Secured 注解來指定的:
class Service { @Secured(user = “ADMIN”) void doSensitiveAction() { // 運行敏感代碼... } }
我們當(dāng)然可以將安全檢查直接編寫到方法中。在實際中,硬編碼橫切關(guān)注點往往會導(dǎo)致復(fù)制 - 粘貼的邏輯,使其難以維護。另外,一旦應(yīng)用需要涉及額外的需求時,如日志、收集調(diào)用指標(biāo)或結(jié)果緩存,直接添加這樣的代碼擴展性不會很好。通過將這樣的功能抽取到 agent 中,方法就能很純粹地關(guān)注其業(yè)務(wù)邏輯,使得代碼庫能夠更易于閱讀、測試和維護。
為了讓我們規(guī)劃的庫保持盡可能得簡單,按照注解的協(xié)議聲明,如果當(dāng)前用戶不具備注解的用戶屬性時,將會拋出IllegalStateException
異常。通過使用 Byte Buddy,這種行為可以用一個簡單的攔截器來實現(xiàn),如下面樣例中的SecurityInterceptor
所示,它會通過其靜態(tài)的 user 域,跟蹤當(dāng)前用戶已經(jīng)進行了登錄:
class SecurityInterceptor { static String user = “ANONYMOUS” static void intercept(@Origin Method method) { if (!method.getAnnotation(Secured.class).user().equals(user)) { throw new IllegalStateException(“Wrong user”); } } }
通過上面的代碼,我們可以看到,即便給定用戶授予了訪問權(quán)限,攔截器也沒有調(diào)用原始的方法。為了解決這個問題,Byte Buddy 有很多預(yù)定義的方法可以實現(xiàn)功能的鏈接。借助MethodDelegation
類的andThen
方法,上述的安全檢查可以放到原始方法的調(diào)用之前,如下面的代碼所示。如果用戶沒有進行認(rèn)證的話,安全檢查將會拋出異常并阻止后續(xù)的執(zhí)行,因此原始方法將不會執(zhí)行。
將這些功能集合在一起,我們就能生成Service
的一個子類,所有帶有注解方法的都能恰當(dāng)?shù)剡M行安全保護。因為所生成的類是 Service 的子類,所以它能夠替代所有類型為Service
的變量,并不需要任何的類型轉(zhuǎn)換,如果沒有恰當(dāng)認(rèn)證的話,調(diào)用doSensitiveAction
方法就會拋出異常:
new ByteBuddy() .subclass(Service.class) .method(ElementMatchers.isAnnotatedBy(Secured.class)) .intercept(MethodDelegation.to(SecurityInterceptor.class) .andThen(SuperMethodCall.INSTANCE))) .make() .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) .getLoaded() .newInstance() .doSensitiveAction();
不過壞消息是,因為實現(xiàn) instrumentation
功能的子類是在運行時創(chuàng)建的,所以除了使用 Java 反射以外,沒有其他辦法創(chuàng)建這樣的實例。因此,所有 instrumentation 類的實例都應(yīng)該通過一個工廠來創(chuàng)建,這個工廠會封裝創(chuàng)建 instrumentation 子類的復(fù)雜性。這樣造成的結(jié)果就是,子類 instrumentation 通常會用于框架之中,這些框架本身就需要通過工廠來創(chuàng)建實例,例如,像依賴管理的框架 Spring
或?qū)ο?- 關(guān)系映射的框架 Hibernate
,而對于其他類型的應(yīng)用來講,子類 instrumentation 實現(xiàn)起來通常過于復(fù)雜。
實現(xiàn)安全功能的 Java agent
通過使用 Java agent,上述安全框架的一個替代實現(xiàn)將會修改Service
類的原始字節(jié)碼,而不是重寫它。這樣做的話,我們就沒有必要創(chuàng)建托管的實例了,只需簡單地調(diào)用
new Service().doSensitiveAction()
即可,如果對應(yīng)的用戶沒有進行認(rèn)證的話,就會拋出異常。為了支持這種方式,Byte Buddy 提供一種稱之為 _rebase 某個類 _ 的理念。當(dāng) rebase 某個類的時候,不會創(chuàng)建子類,所采用的策略是實現(xiàn) instrumentation 功能的代碼將會合并到被 instrument 的類中,從而改變其行為。在添加 instrumentation 功能之后,在被 instrument 的類中,其所有方法的原始代碼均可進行訪問,因此像SuperMethodCall
這樣的 instrumentation,工作方式與創(chuàng)建子類是完全一樣的。
創(chuàng)建子類與 rebase 的行為是非常類似的,所以兩種操作的 API 執(zhí)行方式是一致的,都會使用相同的DynamicType.Builder
接口來描述某個類型。兩種形式的 instrumentation 都可以通過ByteBuddy
類來進行訪問。為了使 Java agent 的定義更加便利,Byte Buddy 還提供了AgentBuilder
類,它希望能夠以一種簡潔的方式應(yīng)對一些通用的用戶場景。為了定義 Java agent 實現(xiàn)方法級別的安全性,將如下的類定義為 agent 的入口點就足以完成該功能了:
class SecurityAgent { public static void premain(String arg, Instrumentation inst) { new AgentBuilder.Default() .type(ElementMatchers.any()) .transform((builder, type) -> builder .method(ElementMatchers.isAnnotatedBy(Secured.class) .intercept(MethodDelegation.to(SecurityInterceptor.class) .andThen(SuperMethodCall.INSTANCE)))) .installOn(inst); } }
如果將這個 agent 打包為 jar 文件并在命令行中進行指定,那么所有帶有Secured
注解的方法將會進行“轉(zhuǎn)換”或重定義,從而實現(xiàn)安全保護。如果不激活這個 Java agent 的話,應(yīng)用在運行時就不包含額外的安全檢查。當(dāng)然,這意味著如果對帶有注解的代碼進行單元測試的話,這些方法的調(diào)用并不需要特殊的搭建過程來模擬安全上下文。Java 運行時會忽略掉無法在 classpath 中找到的注解類型,因此在運行帶有注解的方法時,我們甚至完全可以在應(yīng)用中移除掉安全庫。
另外一項優(yōu)勢在于,Java agent 能夠很容易地進行疊加。如果在命令行中指定多個 Java agent 的話,每個 agent 都有機會對類進行修改,其順序就是在命令行中所指定的順序。例如,我們可以采取這種方式將安全、日志以及監(jiān)控框架聯(lián)合在一起,而不需要在這些應(yīng)用間增添任何形式的集成層。因此,使用 Java agent 實現(xiàn)橫切的關(guān)注點提供了一種更為模塊化的代碼編寫方式,而不必針對某個管理實例的中心框架來集成所有的代碼。
_Byte Buddy 的源碼可以免費地在 GitHub 上獲取到。入門手冊可以在 http://bytebuddy.net上找到。Byte Buddy 當(dāng)前的可用版本是 0.7.4,所有樣例均是基于該版本的。因為其革新性以及對 Java 生態(tài)系統(tǒng)的貢獻,該庫曾經(jīng)在 2015 年獲得過 Oracle 的 Duke’s Choice 獎項。
關(guān)于作者
Rafael Winterhalter是一位軟件咨詢師,在挪威的奧斯陸工作。他是靜態(tài)類型的支持者,對 JVM 有極大的熱情,尤其關(guān)注于代碼 instrumentation、并發(fā)和函數(shù)式編程。Rafael 日常會撰寫關(guān)于軟件開發(fā)的博客,經(jīng)常出席相關(guān)的會議,并被認(rèn)定為 JavaOne Rock Star。在工作以外的編碼過程中,他為多個開源項目做出過貢獻,經(jīng)常會花精力在 Byte Buddy 上,這是一個為 Java 虛擬機簡化運行時代碼生成的庫。因為他的貢獻,Rafael 得到過 Duke’s Choice 獎項。
查看英文原文: Easily Create Java Agents with Byte Buddy
以上就是通過使用Byte Buddy便捷創(chuàng)建Java Agent的詳細內(nèi)容,更多關(guān)于Byte Buddy創(chuàng)建Java Agent的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java簡單數(shù)據(jù)加密方法DES實現(xiàn)過程解析
這篇文章主要介紹了Java簡單數(shù)據(jù)加密方法DES實現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-12-12使用Jenkins來構(gòu)建GIT+Maven項目的方法步驟
這篇文章主要介紹了使用Jenkins來構(gòu)建GIT+Maven項目,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01在Spring?Boot使用Undertow服務(wù)的方法
Undertow是RedHAT紅帽公司開源的產(chǎn)品,采用JAVA開發(fā),是一款靈活,高性能的web服務(wù)器,提供了NIO的阻塞/非阻塞API,也是Wildfly的默認(rèn)Web容器,這篇文章給大家介紹了在Spring?Boot使用Undertow服務(wù)的方法,感興趣的朋友跟隨小編一起看看吧2023-05-05Mybatis實現(xiàn)數(shù)據(jù)的增刪改查實例(CRUD)
本篇文章主要介紹了Mybatis實現(xiàn)數(shù)據(jù)的增刪改查實例(CRUD),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05