Java字節碼3-使用ByteBuddy實現一個Java-Agent

1、概述
在前面兩節中,咱們實現了Agent,可是其不管在使用方式和功能上面都有必定的侷限性。本文咱們藉助字節碼工具ByteBuddy,寫出高級的Agent。java

ByteBuddy不單單是爲了生成Java-Agent,它提供的API甚至能夠改變重寫一個Java類,本文咱們使用其API實現和第二節同樣的功能,給目標類中的函數統計其調用耗時。apache

2、實現
一、修改pom.xml
本節和上節的不一樣點,主要有兩個。一個是引入ByteBuddy的依賴,另外一個是須要將ByteBuddy的包經過shade打入到Agent中。下面只截取關鍵代碼:maven

<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy</artifactId>
    <version>1.5.7</version>
</dependency>

<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy-agent</artifactId>
    <version>1.5.7</version>
</dependency>


<plugin> 
  <groupId>org.apache.maven.plugins</groupId>  
  <artifactId>maven-shade-plugin</artifactId>  
  <executions> 
    <execution> 
      <phase>package</phase>  
      <goals> 
        <goal>shade</goal> 
      </goals> 
    </execution> 
  </executions>  
  <configuration> 
    <artifactSet> 
      <includes> 
        <include>javassist:javassist:jar:</include>  
        <include>net.bytebuddy:byte-buddy:jar:</include>  
        <include>net.bytebuddy:byte-buddy-agent:jar:</include> 
      </includes> 
    </artifactSet> 
  </configuration> 
</plugin>

二、實現一個Agent
與以前相同的是,這裏仍然是在premain處進行處理。經過AgentBuilder方法,生成一個Agent。這裏有兩點須要特別說明:其一是在AgentBuilder.type處,這裏能夠指定須要攔截的類;其二是在builder.method處,這裏能夠指定須要攔截的方法。固然其API支持各類isStatic、isPublic等等一系列方式。ide

public class MyAgent {

    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("this is an perform monitor agent.");

        AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
            @Override
            public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
                                                    TypeDescription typeDescription,
                                                    ClassLoader classLoader) {
                return builder
                        .method(ElementMatchers.<MethodDescription>any()) // 攔截任意方法
                        .intercept(MethodDelegation.to(TimeInterceptor.class)); // 委託
            }
        };

        AgentBuilder.Listener listener = new AgentBuilder.Listener() {
            @Override
            public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, DynamicType dynamicType) {}

            @Override
            public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) { }

            @Override
            public void onError(String typeName, ClassLoader classLoader, JavaModule module, Throwable throwable) { }

            @Override
            public void onComplete(String typeName, ClassLoader classLoader, JavaModule module) { }
        };

        new AgentBuilder
                .Default()
                .type(ElementMatchers.nameStartsWith("com.example.demo")) // 指定須要攔截的類
                .transform(transformer)
                .with(listener)
                .installOn(inst);
    }
}

三、實現一個用來委託的Interceptor

在上一步實現Transformer的過程當中,委託了一個TimeInterceptor.class。下面是其實現方式,整個的try語句是原有的代碼執行,咱們在以前打了時間戳,並在其結束後,計算並打印了其調用耗時。函數

public class TimeInterceptor  {
    @RuntimeType
    public static Object intercept(@Origin Method method,
                                   @SuperCall Callable<?> callable) throws Exception {
        long start = System.currentTimeMillis();
        try {
            // 原有函數執行
            return callable.call();
        } finally {
            System.out.println(method + ": took " + (System.currentTimeMillis() - start) + "ms");
        }
    }
}

3、運行

這裏須要注意的是,咱們定義的包路徑要和Agent中定義的相同,不然Agent沒法Hook到這個類及其方法。工具

package com.example.demo;

public class AgentTest {

    private void fun1() throws Exception {
        System.out.println("this is fun 1.");
        Thread.sleep(500);
    }

    private void fun2() throws Exception {
        System.out.println("this is fun 2.");
        Thread.sleep(500);
    }

    public static void main(String[] args) throws Exception {
        AgentTest test = new AgentTest();
        test.fun1();
        test.fun2();

    }
}

結果:ui

this is an perform monitor agent.
this is fun 1.
private void com.example.demo.AgentTest.fun1() throws java.lang.Exception: took 501ms
this is fun 2.
private void com.example.demo.AgentTest.fun2() throws java.lang.Exception: took 500ms
public static void com.example.demo.AgentTest.main(java.lang.String[]) throws java.lang.Exception: took 1001ms
相關文章
相關標籤/搜索