ByteBuddy-Javaagent的使用

ByteBuddy官方文档

https://bytebuddy.net/#/tutorial-cn

反编译类网址

http://www.javare.cn/

ByteBuddy之Javaagent使用,实现类加载拦截,修改类的字节码,实现一些业务逻辑。例如采集方法耗时…


添加bytebuddy的依赖:

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

方法代理

方法代理:类似与SpringAOP,创建一个代理对象,去调用目标方法。bytebuddy的方法代理是在目标方法中调用代理类的方法,然后再回调的本类的源方法。

代码演示

public class UserService {
    public String selectUser(String username) {
        return "张三";
    }
}
public class AgentMain {
    public static void premain(String agentArgs, Instrumentation instrumentation) {
        AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
            public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription,
                                                    ClassLoader classLoader, JavaModule module, ProtectionDomain protectionDomain) {
                // 增强方法{selectUser}
                return builder.method(ElementMatchers.named("selectUser"))
                        // 设置拦截器
                        .intercept(MethodDelegation.to(AgentMain.Interceptor.class));
            }
        };
        new AgentBuilder.Default()
                // 忽略掉不增强的类
                .ignore(nameStartsWith("net.bytebuddy."))
                // 增强的类
                .type(ElementMatchers.named("com.liuqi.service.UserService"))
                // 增强的类需 增强的方法实现
                .transform(transformer)
                // 注册增强类监听器
                .with(new AgentBuilder.Listener() {
                    public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {}
                    public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) {
                        String className = typeDescription.getName();
                        System.out.println("增强类: " + className);
                    }
                    public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) {}
                    public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
                        System.out.println("增强类失败: " + typeName + "; 失败原因: " + throwable);
                    }
                    public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { }
                })
                // 监听类加载
                .installOn(instrumentation);

        // 增强后的类, 写入至文件, 便于观察
        instrumentation.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader, String className,
                                    Class<?> classBeingRedefined,
                                    ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
                if (className.contains("UserService")) {
                    try {
                        String substring = className.substring(className.lastIndexOf("/"));
                        Files.write(new File("C:/Users/86187/Desktop/bytebuddy-class/" + substring + ".class").toPath(), classfileBuffer);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                return classfileBuffer;
            }
        });
    }

    public static class Interceptor {
        @RuntimeType
        public static Object intercept(@This Object target,    // 当前拦截的目标对象{this}
                                @AllArguments Object[] allArguments,   // 方法入参
                                @SuperCall Callable<?> superCall,      // 代理对象
                                @Origin Method method                  // 当前拦截的目标方法
        ) throws Throwable {
            Object result = null;  // 方法返回结果
            try {
                System.out.println("前置拦截");
                // 执行目标方法
                result = superCall.call();
                // 返回目标方法执行结果
                return result;
            } catch (Throwable t) {
                System.out.println("异常拦截");
                throw t;
            } finally {
                System.out.println("后置拦截: 方法返回值 = " + result);
            }
        }
    }
}

运行效果

在这里插入图片描述
查看增强后的类

可以看到bytebuddy为UserService类又生成了一个类。
在这里插入图片描述
反编译 UserService

标注synthetic说明是编译器生成的。

package com.liuqi.service;

import com.liuqi.agent.AgentMain.Interceptor;
import com.liuqi.service.UserService.auxiliary.VdebFR3h;
import java.lang.reflect.Method;

public class UserService {

   // $FF: synthetic field	
   // 声明方法字段
   private static final Method cachedValue$wCBkyXnP$fi9hdm2;
   // 源目标方法, 字节码已被修改, 调用逻辑为:调用拦截的方法
   public String selectUser(String var1) {
   	  // new VdebFR3h(this, var1) 创建代理对象
      return (String)Interceptor.intercept(
      			this,	// 当前实例对象
       			new Object[]{var1}, // 方法入参
       			new VdebFR3h(this, var1), // 代理对象
       			cachedValue$wCBkyXnP$fi9hdm2	// 拦截方法
       );
   }	
   // $FF: synthetic method
   // 源目标方法被重命名, 
   private String selectUser$original$uTSGTdQZ(String username) {
      return "张三";
   }
	// 静态初始化代码块, bytebuddy生成的, 
   static {
      ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.dynamic.Nexus").getMethod("initialize", new Class[]{Class.class, Integer.TYPE}).invoke((Object)null, new Object[]{UserService.class, Integer.valueOf(-449407519)});
      // // 为字段 {Method: cachedValue$wCBkyXnP$fi9hdm2} 赋值
      cachedValue$wCBkyXnP$fi9hdm2 = UserService.class.getMethod("selectUser", new Class[]{String.class});
   }

   // $FF: synthetic method
   // 为代理类提供的调用方法, 取调用目标方法.
   // 因为目标方法被修饰了{private}, 非本类不能访问.
   final String selectUser$original$uTSGTdQZ$accessor$wCBkyXnP(String var1) {
      return this.selectUser$original$uTSGTdQZ(var1);
   }
}

反编译 UserService a u x i l i a r y auxiliary auxiliaryVdebFR3h

package com.liuqi.service;

import com.liuqi.service.UserService;
import java.util.concurrent.Callable;

// $FF: synthetic class
class UserService$auxiliary$VdebFR3h implements Runnable, Callable {
   // 目标方法实例
   private UserService argument0;
   // 目标方法入参
   private String argument1;
   // 调用目标方法
   public Object call() throws Exception {
      return this.argument0.selectUser$original$uTSGTdQZ$accessor$wCBkyXnP(this.argument1);
   }
   // 调用目标方法, 无返回值
   public void run() {
      this.argument0.selectUser$original$uTSGTdQZ$accessor$wCBkyXnP(this.argument1);
   }
   // 构造函数
   UserService$auxiliary$VdebFR3h(UserService var1, String var2) {
      this.argument0 = var1;
      this.argument1 = var2;
   }
}

方法代理 - 可修改入参

自定义一个类。

public interface MyCallable {
    /**
     * 定义一个方法, 方法名随意 {此类中只能有这一个方法, 只能有一个入参}
     *
     * @param args  方法入参
     * @return
     */
    Object call(Object[] args);
}

在设置拦截器时,写入以下配置:

MethodDelegation.withDefaultConfiguration().withBinders(Morph.Binder.install(MyCallable.class))

AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
            public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription,
                                                    ClassLoader classLoader, JavaModule module, ProtectionDomain protectionDomain) {
                // 增强方法{selectUser}
                return builder.method(ElementMatchers.named("selectUser"))
                        // 设置拦截器
                        .intercept(MethodDelegation.withDefaultConfiguration()  // 写入某些配置
                                    .withBinders(Morph.Binder.install(MyCallable.class))    // 设置一个代理调用类
                                    .to(AgentMain.Interceptor.class));
            }
        };

拦截器修改以下配置:

使用@Morph MyCallable接收代理对象

public static class Interceptor {
        @RuntimeType
        public static Object intercept(@This Object target,    // 当前拦截的目标对象{this}
                                @AllArguments Object[] allArguments,   // 方法入参
                                @Origin Method method,                  // 当前拦截的目标方法
                                @Morph MyCallable superCall             // 代理对象
        ) throws Throwable {
            Object result = null;  // 方法返回结果
            try {
                System.out.println("前置拦截");
                // 修改参数
                allArguments[0] = "1111";
                // 执行目标方法
                result = superCall.call(allArguments);
                // 返回目标方法执行结果
                return result;
            } catch (Throwable t) {
                System.out.println("异常拦截");
                throw t;
            } finally {
                System.out.println("后置拦截: 方法返回值 = " + result);
            }
        }
    }

方法字节码修改

创建拦截器

package com.liuqi.agent;

import net.bytebuddy.asm.Advice;
import java.lang.reflect.Method;

/**
 * @author liuqi
 * @date 2022/12/15 12:27
 * @since 1.0.0
 **/
public class AdviceInterceptor {
    /**
     * 方法运行前: 执行代码的片段, 必须是静态的方法
     */
    @Advice.OnMethodEnter(suppress = Throwable.class)
    public static void enter(@Advice.This Object that, // 当前实例
                      @Advice.AllArguments Object[] args,   // 方法入参
                      @Advice.Origin Method method)     // 目标方法
    {
        System.out.println("前置拦截 ");
    }

    /**
     * 方法退出前, 代码执行片段, 必须是静态的方法
     */
    @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
    public static void exit(@Advice.This Object that, // 当前实例
                     @Advice.AllArguments Object[] args,   // 方法入参
                     @Advice.Origin Method method,   // 目标方法
                     @Advice.Return Object result,  // 返回值
                     @Advice.Thrown Throwable t     // 异常, 如果没有异常, 则 = null
    )
    {
        if (t != null) {
            System.out.println("异常拦截");
        } else {
            System.out.println("后置拦截 ");
        }
    }
}

设置拦截器

transform方法中填写new AgentBuilder.Transformer.ForAdvice()实例。

advice方法中有两个入参:

  • 参数 0:增强方法匹配器
  • 参数 1:拦截器类名
new AgentBuilder.Default()
                // 忽略掉不增强的类
                .ignore(nameStartsWith("net.bytebuddy."))
                // 增强的类
                .type(ElementMatchers.named("com.liuqi.service.UserService"))
                // 增强的类需 增强的方法实现
                .transform(
                        new AgentBuilder.Transformer.ForAdvice()
                                .advice(ElementMatchers.named("selectUser"), "com.liuqi.agent.AdviceInterceptor")
                )
                // 监听类加载
                .installOn(instrumentation);

查看增强后的类

可以看到bytebuddy没有生成其他的代理类。
在这里插入图片描述
反编译 UserService

可以看到,此增强方式,是将拦截器中的代码直接拷贝至此方法中

package com.liuqi.service;


public class UserService {

   public String selectUser(String var1) {
      try {
         System.out.println("前置拦截 ");
      } catch (Throwable var7) {
         ;
      }

      String var2;
      Throwable username;
      label31: {
         String var10000;
         try {
            var10000 = "张三";
         } catch (Throwable var8) {
            username = var8;
            var2 = null;
            break label31;
         }

         var2 = var10000;
         username = null;
      }

      try {
         if(username != null) {
            System.out.println("异常拦截");
         } else {
            System.out.println("后置拦截 ");
         }
      } catch (Throwable var6) {
         ;
      }

      if(username != null) {
         throw username;
      } else {
         return var2;
      }
   }

   static {
      ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.dynamic.Nexus").getMethod("initialize", new Class[]{Class.class, Integer.TYPE}).invoke((Object)null, new Object[]{UserService.class, Integer.valueOf(250183171)});
   }
}

总结

方法代理

方法代理的方式,会为每个方法增强都会生成一个代理对象。例如一个类中,需要增强两个方法,则会生成两个代理类。

skywalking是使用此方式。

在这里插入图片描述
字节码方法修改

此方式只是将代理类的执行代码拷贝至目标方法中,并不会生成额外的代理类。即使一个类中增强了多个方法。

OpenTelemetry使用此方式。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Me_Liu_Q

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值