美文网首页javaagent
Java字节码3-使用ByteBuddy实现一个Java-Age

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

作者: 唐影若凡 | 来源:发表于2017-03-06 10:31 被阅读750次

    声明:原创文章,转载请注明出处。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并增强了其调用方法。

    相关文章

      网友评论

      本文标题:Java字节码3-使用ByteBuddy实现一个Java-Age

      本文链接:https://www.haomeiwen.com/subject/tkfdwttx.html