不用 Jar 包的 Agent ?幾行代碼實現運行時加強


提起 JavaAgent,不少人都說幾句,就像古龍武俠小說裏的「孔雀翎」,威力很大,江湖上都是它的傳說。但真的見識過的人並沒幾個。
javascript


JavaAgent 雖然說沒這麼神祕,但也一直給人曲高和寡的感受,除了一些中間件產品、大型的框架中使用外,在業務中一直不多出現。css


緣由可能有不少,一來是可能確實不須要,再者須要開發獨立的 Agent Jar 文件,在 Jar 內對類的 transform 開發也並不容易。java


咱們知道,不管是啓動時的 Java Agent,仍是運行時的動態 attach 到遠程JVM, 都是爲了拿到 Instrument,對 class 的字節碼進行修改。這麼底層的東西,固然使用起來讓人不太容易下手。程序員

不過就像機器語言不方便,人們發明了彙編語言,又發現了高級語言。對於字節碼的操做也相似,人們以爲直接操做字節碼有難度,並且須要深刻理解 JVM 規範,具體什麼位置多少字節表明啥,這不是通常人喜歡的,因而 ASM 框架出現了;但仍是有規範的影子,不太「高級」,因而又出現了Javassist 這一類的「高級」工具。
web


咱們今天要說的這個工具,和 Javassist 相似,都提供了更高層的API,來操做class,對普通程序員更友好,除此以外呢?
typescript


就像今天人們購物、讀書等,都更相信專業的平臺、或者專家的推薦,像XX嚴選,XX讀書會推薦。今天說的這個工具是Duke 的推薦,對,就是它, Java 的吉祥物,這個小胖子。今天的這個工具在 2015年被 Oracle 評選爲「Duke's Choice award」。數據庫




除了Duke,框架也獲得了衆多開發者的承認,每一年有七千多萬次的下載。
swift


這個工具是:Bytebuddy。 tomcat

從名字你就看的出來,它立志要作字節碼的好夥伴。因此在不少開源框架裏也能看到它的身影。微信


既然已經有了很多的工具, byteBuddy能帶來什麼不同呢?


除了API 上的簡潔易操做,官方本身也大字強調了運行時動態的「代碼生成和字節碼操做」,不須要再借助 Java 編譯器。


來看看官網是怎麼自我介紹的,後面再附上幾個代碼片斷,就能很快 Get 到了。

官網:https://bytebuddy.net/

Byte Buddy is a code generation and manipulation library for creating and modifying Java classes during the runtime of a Java application and without the help of a compiler. Other than the code generation utilities that ship with the Java Class Library, Byte Buddy allows the creation of arbitrary classes and is not limited to implementing interfaces for the creation of runtime proxies. Furthermore, Byte Buddy offers a convenient API for changing classes either manually, using a Java agent or during a build.


閱讀理解開始。重點你必定會看到了:

  • 「code generation」

  • 「creating and modifying Java classes」


做者貼心的加了一段小字來描述框架的優點。選重點的說就是:

  1. 不須要理解字節碼,也不須要理解class 文件格式

  2. API 非侵入,設計簡潔易懂

  3. 高度可定製,能夠任意自定義


我本身認爲該把這點也加上,不寫 Java Agent 也能夠 Attach 到 JVM 上,把 ByteBuddy 本身當成一個Agent,運行時直接install。Cool。


不寫JVM Agent 也能對類攔截和修改,咱們來認識下揭開字節碼修改黑魔法的傢伙。

爲了對 Class 進行一些操做,咱們通常都離不了 JVM Agent。不論是啓動時直接鏈接,仍是運行時動態的 Attach到對應的JVM 上,都須要 Agent。也就是咱們熟悉的premainagentmain 的觸發入口,經過它們,咱們才能拿到 Instrumentation,從而進行 transformredeine


但這個東西的使用,給人老是「陽春白雪」的感受,讓人以爲是黑魔法同樣,通常不會輕易嘗試使用。

有了ByteBuddy,就不用再羨慕一些框架的「運行時加強」,「動態織入」等等,均可以實現。

如何上手呢?

只須要下載 Jar 文件或者 Maven 添加依賴以後就能狂奔了。


好比官方的這個   HelloWorld

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!"));


直接把 Object 的 toString 方法改寫了。


再好比咱們能夠在開發 Java Agent的時候使用這個夥計

public static void premain(String args, Instrumentation inst) { AgentBuilder agentBuilder = new AgentBuilder.Default();        AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() { public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {                String className = typeDescription.getCanonicalName(); builder = builder.method(ElementMatchers.any())//匹配任意方法 .intercept(MethodDelegation.to(new SimplePackageInstanceMethodInterceptor())); return builder; } };         agentBuilder = agentBuilder.type(ElementMatchers.nameStartsWith("com.example.hello.sample")).transform(transformer); agentBuilder.installOn(inst); }


在類裏進行攔截匹配的時候,能夠經過類名來限定,同時以不一樣的模式去匹配方法名等,這裏的ElementMatchers能夠用在類名與方法名等匹配場景中

 //ElementMatchers.named("abc") // 特定名稱的方法 //ElementMatchers.nameStartsWith("hello") // 以什麼開頭的 //ElementMatchers.nameEndsWith("test")   // 以什麼結尾的


咱們看到前面的代碼中 agentBuilder.installOn(inst);

經過 JavaAgent的Instrument 進行類修改。

AgentBuilder 還提供了一個神奇的方法:

agentBuilder.installOnByteBuddyAgent();

這樣無須提供 Jar 文件也同樣能實現運行時加強。不過須要注意,這樣使用時,必定要先執行這行代碼,這也是其實現的祕密:

 ByteBuddyAgent.install();


由於 ByteBuddy 本身作爲一個 Jar 也 Attach ,而後再將其它後續的加強代碼加入,像不像「特洛伊木馬」 :)


另外, ByteBuddy 還支持相似於 AOP 的 Advice實現,在攔截指定方法後能夠實現OnMethodEnter 和 OnMethodExit 的控制,在這其中,能夠完成繞過用戶代碼,執行自定義內容的邏輯。


我們在開始的時候,還提到了代碼的生成。這在 ByteBuddy 看來也是易如反掌。


和上面的代碼同樣,先要拿到 AgentBuilder,以後在執行 tranform的時候,直接指定方法名,以及對應的參數,訪問控制符等。

 DynamicType.Builder.MethodDefinition.ExceptionDefinition<?> hello =                                builder.defineMethod(methodName, types, Visibility.PUBLIC) .withParameters(m.getParameters().asTypeList());


再好比在運行時給一個方法增長註解,

builder.method(ElementMatchers.named("methodName")).intercept(SuperMethodCall.INSTANCE)                           .annotateMethod(AnnotationDescription.Builder.ofType(TestAnnotation.class) .define("testValue", 123).build());



是否是功能很強大?


更多的用法,能夠參考官方的Github或者官網。


歡迎交流。


相關閱讀

Sentinel 是怎樣攔截異常流量的?

MySQL: 喂,別走,聽我解釋一下好嗎?

多表查詢用什麼聯接?別信感受,用數聽說話

一個數據庫SQL查詢的數次輪迴

數據庫是咋工做的?(一)

憑什麼讓日誌先寫?

Java七武器系列長生劍 -- Java虛擬機的顯微鏡 Serviceability Agent

Java七武器系列霸王槍 -- 線程狀態分析 jstack

Java七武器系列孔雀翎-- 問題診斷神器BTrace

嵌套事務、掛起事務,Spring 是怎樣給事務又實現傳播特性的?

怎樣閱讀源代碼?





源碼|實戰|成長|職場


這裏是「Tomcat那些事兒

請留下你的足跡

咱們一塊兒「終身成長」

本文分享自微信公衆號 - Tomcat那些事兒(tomcat0000)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索