一個簡單的Agent

 

Java agent是用一個簡單的jar文件來表示的。跟普通的Java程序很類似,Java agent定義了一些類做爲入口點。 這些做爲入口點的類須要包含一個靜態方法,這些方法會在你本來的Java程序的main方法調用以前被調用:git

1github

2web

3apache

4編程

5框架

class MyAgent {工具

  public static void premain(String args, Instrumentation inst) {ui

    // implement agent here ...編碼

  }spa

}

關於處理Java agent時最有趣的部分,是premain方法中的第二個參數。這個參數是以一個Instrumentation接口的實現類實例的形式存在的。這個接 口提供了一種機制,可以經過定義一個ClassFileTransformer,來干預對Java類的加載過程。有了這種轉設施,咱們就可以在Java類 被使用以前,去實現對類邏輯的強化。

這個API的使用一開始看上去不那麼直觀,極可能是一種新的(編程模式)挑戰。Class文件的轉換是經過修改編 譯事後的Java類字節碼來完成的。 實際上,JVM並不知道什麼是Java語言, 它只知道什麼是字節碼。也正是由於字節碼的抽象特性,才讓JVM可以具備運行多種語言的能力,例如Groovy, Scala等等。這樣一來,一個註冊了的類文件轉換器就只須要負責把一個字節碼序列轉換成另一個字節碼序列就能夠了。

盡 管已經有了像ASMBCEL這樣的類庫,提供了一些簡易的API,可以對編譯過的Java類進行操做,可是使用這些庫的門檻較高,須要開發者對原始的字 節碼的工做原理有充分的瞭解。更可怕的是,想直接操做字節碼並作到不出問題,這基本上就是一個冗長拉鋸的過程,甚至很是細微的錯誤,JVM也會直接拋出又 臭又硬的VerifierError。不過還好,咱們還有更好,更簡單的選擇,來對字節碼進行操做。

Byte Buddy這是一個我編寫,並負責維護的工具庫。這個庫提供了簡潔的API,用來對編譯後的Java字節碼進行操做,也能夠用來建立Java agent. 從某些方面來看,Byte Buddy也是一個代碼生成的工具庫,這和cglib以及Javassit的功能很相似。然而,跟他們不一樣的是,Byte Buddy還可以提供統一的API,實現子類化,以及重定義現有類的功能。在本文中,咱們只會研究如何用Java agent來重定義一個類。若是讀者有更多的興趣,能夠參照Byte Buddy’s webpage which offers a detailed tutorial ,那個裏面有很詳細的描述。

使用Byte Buddy建立simple agent

Byte Buddy提供的一種定義手段採用了依賴注入的方法。其原理是這樣的:使用一個攔截器類——這個類是一個POJO——來得到標註參數所須要的信息。例如: 將Byte Buddy的@Origin標註使用在一個Method類型的參數上,Byte Buddy便可推演出攔截器目前要攔截的就是method變量。這樣,咱們就能夠定義一個泛型的攔截器,只要method一出現,就會被攔截器攔截。

1

2

3

4

5

class LogInterceptor {

  static void log(@Origin Method method) {

    Logger.log(method + " was called");

  }

}

固然,Byte Buddy能夠做用於多個標註上。

可是,這些攔截器如何可以表示咱們提出的日誌框架所須要的代碼邏輯呢?到目前爲止,咱們僅僅是定義了一個攔截器,用來攔截咱們的method調用。還缺乏對 於method所在的原始代碼序列的調用。幸運的是,Byte Buddy提供的手段是可組合(compose)的。首先咱們定義一個MethodDelegation類,並將其組合到LogInterceptor 中,這個攔截器類會在每一次method被調用的時候去默認調用攔截器的靜態方法。以此爲起點,咱們能夠經過一種序列調用的方式,將代理類和原先調用 method的代碼組合起來,就跟Super MethodCall表示的同樣:

1

2

3

4

class LogAgent {

MethodDelegation.to(LogInterceptor.class)

  .andThen(SuperMethodCall.INSTANCE)

}

最後,咱們還須要通知Byte Buddy,將被攔截的方法與特定的邏輯綁定。就像咱們在前面闡述的同樣,咱們想把一段邏輯(就是記錄日誌的功能——譯者注)施加到每個加了@Log標 注的地方。在Byte Buddy中,經過使用ElementMatcher方法,(被標註的)方法就會被識別出來,這和Java 8的斷言機制很相似。在靜態工具類ElementMatcher中,咱們能夠用相應的matcher來識別咱們(用@Log)標註後的方 法:ElementMatchers.isAnnotatedWith(Log.class)。

通 過上述的方式,咱們就實現一個agent的定義,能夠完成咱們提出logging framework的要求。就跟咱們在前文敘述的原理同樣,Byte Buddy提供了一套工具API來構建Java agent,這些工具API則是基於可對(編譯後的)class進行修改的(JavaEE原生)API。就跟下面這段API同樣,其設計上與面向領域語言 類似,從代碼的字面上就能夠輕鬆弄懂其含義。顯然,定義一個agent就僅僅須要幾行代碼而已:

1

2

3

4

5

6

7

8

9

10

11

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 agent代碼不會對原有的代碼產生干擾。對於已有的代碼來講,附加的邏輯代碼就彷彿是直接把硬編碼插入到帶有標註的方法處同樣(相似於C++內聯的效果——譯者注)

相關文章
相關標籤/搜索