Java字节码3-使用ByteBuddy实现一个Java-Agent

声明:原创文章,转载请注明出处。http://www.jianshu.com/p/fe1448bf7d31

Java字节码系列
Java字节码1-Agent简单上手
Java字节码2-instrument初体验
Java字节码3-使用ByteBuddy实现一个Java-Agent
Java字节码4-使用Java-Agent实现一个JVM监控工具
本系列代码可见:https://github.com/hawkingfoo/demo-agent

一、概述

在前面两节中,我们实现了Agent,但是其无论在使用方式和功能上面都有一定的局限性。本文我们借助字节码工具ByteBuddy,写出高级的Agent。

ByteBuddy不仅仅是为了生成Java-Agent,它提供的API甚至可以改变重写一个Java类,本文我们使用其API实现和第二节一样的功能,给目标类中的函数统计其调用耗时。

二、实现

1、修改pom.xml

本节和上节的不同点,主要有两个。一个是引入ByteBuddy的依赖,另一个是需要将ByteBuddy的包通过shade打入到Agent中。下面只截取关键代码:

<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>

2、实现一个Agent

与之前相同的是,这里仍然是在premain处进行处理。通过AgentBuilder方法,生成一个Agent。这里有两点需要特别说明:其一是在AgentBuilder.type处,这里可以指定需要拦截的类;其二是在builder.method处,这里可以指定需要拦截的方法。当然其API支持各种isStatic、isPublic等等一系列方式。

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);
    }
}

3、实现一个用来委托的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");
        }
    }
}

三、运行

这里需要注意的是,我们定义的包路径要和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();

    }
}

结果:

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

可以看到,我们的Agent成功Hook并增强了其调用方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • title: Android AOP之字节码插桩author: 陶超description: 实现数据收集SDK时...
    陶菜菜阅读 38,772评论 40 182
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 136,791评论 19 139
  • 2017.10.13 红再低调依旧亮眼 此刻,我在去往北京的列车上,耳机播放着飘向北方,这首歌是我在半年前就想着去...
    一目_Yimu阅读 310评论 0 1
  • 油菜花开了 漫山遍野,阡陌纵横 一片连着一片 苑如金灿灿的宫殿 迷了伊人 古梯田醒了 层层叠叠,蜿蜿蜒蜒 暖暖的阳...
    彩云_57b1阅读 910评论 9 12
  • 真是受不了肾宝片广告里的那个女的, 先是一脸无辜的问自己男人: 是不是肾透支了? 是不是透支了你丫的不知道啊? 事...
    兴豪文化传媒阅读 548评论 0 0

友情链接更多精彩内容