ByteBuddy实战笔记
摘要:本文介绍了Java字节码增强框架ByteBuddy的核心功能,包括动态生成类、方法拦截和属性修改等技术。主要内容涵盖:1)动态类的三种生成方式(subclass/rebase/redefine)及其命名策略;2)方法拦截的不同实现形式,包括实例方法、静态方法和构造方法的动态修改;3)使用注解实现参数绑定和委托调用;4)Java Agent的实现方式和Advice内联织入技术。文中详细说明了各
运行时动态快速生成类,快速调用生成的类,javaagent插桩代理
插桩:对字节码进行修改或增强 stub instrumentation
埋点:插桩是埋点的一种方式
生成的类的命名
若不指定命名规则:
1. 父类是jdk,net.bytebuddy.renamed.java.lang.Object$ByteBuddy$一串uuid
2. 父类是自定义的类,父类全限定名$ByteBuddy$一串uuid

自定义命名策略,注意默认不能是非法类名,可指定.with(TypeValidation.DISABLED)不校验
NamingStrategy namingStrategy = new NamingStrategy.SuffixingRandom("a.b.1SubObj");
new ByteBuddy()
.with(TypeValidation.DISABLED)
.with(namingStrategy)
.subclass(Object.class)
.name("newClass") // 会覆盖之前指定的命名策略,只生成全限定名为newClass的类
.make()
.saveIn(dir);
链式执行
不管是否agent,都可定义要拦截的类后再增强对应的方法
动态增强的三种方式
- subclass 新建一个子类
- rebase 保留原方法并新增一个名字加了”$original$随机数“后缀的方法
- redefine 原方法不再保留
增加方法
DynamicType.Unloaded<Target> unloaded = new ByteBuddy().
redefine(Target.class)
.name("TestAddMethod")
.defineMethod("walk", String.class, Modifier.PUBLIC + Modifier.FINAL)
.withParameter(String[].class, "args")
.intercept(FixedValue.value("新增加的方法"))
.make();
增加属性
DynamicType.Unloaded<?> unloaded = new ByteBuddy()
.subclass(Object.class)
.name("MyClass")
.defineField("field", String.class, Modifier.PUBLIC)
.make();
拦截方法委托
DynamicType.Unloaded<Target> unloaded = new ByteBuddy()
.subclass(Target.class)
.name("TestMethodDelegation")
.method(named("run"))
.intercept(MethodDelegation.to(TargetInterceptor.class)) // 委托给同名的静态方法
.method(named("saySomething"))
.intercept(MethodDelegation.to(new TargetInterceptor())) // 委托给同名的成员方法
.make();
指定拦截方法的入参个数和返回类型
named("toString").and(returns(String.class)).and(takesArguments(0))
动态修改实例方法
DynamicType.Unloaded<Target> unloaded = new ByteBuddy()
.subclass(Target.class)
.name("TestMethodDelegationAnnotation")
.method(named("run"))
.intercept(MethodDelegation.to(TargetAnnotationInterceptor.class))
.make();
// 使用注解方法增强
public class TargetAnnotationInterceptor {
@RuntimeType // 注解这是拦截方法,即使签名和返回值不同。运行期间bytebuddy会自动给注解修饰的参数赋值
public static Object inject(
@This Object targetObject, // 被拦截的目标对象,只有拦截实例方法或构造方法时可用
@Origin Method targetMethod, // 被拦截的目标方法,实例或静态方法都可用
@AllArguments Object[] targetMethodArgs, // 目标方法的参数
@Super Object superObject, // 被拦截的父类对象,只有拦截实例方法时可用
@SuperCall Callable<?> callable // 用于调用目标方法(原始方法)
) {
System.out.println("targetObject: " + targetObject);
System.out.println("targetMethod.getName(): " + targetMethod.getName());
System.out.println("Arrays.toString(targetMethodArgs): " + Arrays.toString(targetMethodArgs));
System.out.println("superObject: " + superObject);
try {
return callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
动态修改静态方法
不可用redefine,因为不会保留原始方法,无法拦截。也不能用subclass
DynamicType.Unloaded<Target> unloaded = new ByteBuddy()
.rebase(Target.class)
.name("TestInjectStaticMethod")
.method(named("walk").and(isStatic()))
.intercept(MethodDelegation.to(TargetStaticMethodInterceptor.class))
.make();
public class TargetStaticMethodInterceptor {
@RuntimeType // 注解这是拦截方法,即使签名和返回值不同。运行期间bytebuddy会自动给注解修饰的参数赋值
public static Object inject(
@Origin Class<?> clazz,
@Origin Method targetMethod, // 被拦截的目标方法,实例或静态方法都可用
@AllArguments Object[] targetMethodArgs, // 目标方法的参数
@SuperCall Callable<?> callable // 用于调用目标方法
) {
System.out.println("clazz: " + clazz);
System.out.println("targetMethod.getName(): " + targetMethod.getName());
System.out.println("Arrays.toString(targetMethodArgs): " + Arrays.toString(targetMethodArgs));
try {
return callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
动态修改入参
DynamicType.Unloaded<Target> unloaded = new ByteBuddy()
.subclass(Target.class)
.name("TestMethodDelegationArgsAnnotation")
.method(named("shout"))
.intercept(MethodDelegation
.withDefaultConfiguration()
.withBinders(Morph.Binder.install(MyCallable.class))
.to(TargetAnnotationArgsInterceptor.class))
.make();
动态修改构造器
DynamicType.Unloaded<Target> unloaded = new ByteBuddy()
.subclass(Target.class)
.name("TestInjectConstructor")
.constructor(any())
.intercept(SuperMethodCall.INSTANCE.andThen( // 指定在构造方法执行完成后再委托给拦截器
MethodDelegation.to(TargetConstructorInterceptor.class)))
.make();
java agent
配置
vm配置增加 -javaagent:xx.jar,可以在=后面加参数
premain方法
在main方法被执行前自动调用
agentArgs是传给javaagent的参数
public static void premain(String agentArgs, Instrumentation instrumentation) {
LOGGER.info("Starting flink operator input record proxy agent.");
new AgentBuilder.Default()
.type(ElementMatchers.hasSuperType(ElementMatchers.named("org.apache.flink.streaming.api.operators.AbstractStreamOperator")))
.transform((
builder, type, classLoader, module) ->
builder.method(ElementMatchers.named("setKeyContextElement1"))
.intercept(MethodDelegation.to(OperatorInputRecordInterceptor.class)))
.installOn(instrumentation);
LOGGER.info("Flink proxy agent started.");
}
指定拦截类
// 拦截SourceOutput接口的实现类,非接口本身
.type(hasSuperType(ElementMatchers.named("org.apache.flink.api.connector.source.SourceOutput")).and(not(isInterface())))
常用注解
@This Object targetObject, // 被拦截的目标对象,只有拦截实例方法或构造方法时可用
@Origin Method targetMethod, // 被拦截的目标方法,实例或静态方法都可用
@AllArguments Object[] targetMethodArgs, // 目标方法的参数
@Super Object superObject, // 被拦截的父类对象,只有拦截实例方法时可用
@SuperCall Callable<?> callable, // 用于调用目标方法
@FieldValue("name") String name // 可用于访问私有变量
|
注解 |
说明 |
|
@Argument |
绑定单个参数 |
|
@AllArguments |
绑定所有参数的数组 |
|
@This |
当前被拦截的、动态生成的那个对象 |
|
@Super |
当前被拦截的、动态生成的那个对象的父类对象 |
|
@Origin |
可以绑定到以下类型的参数: Method 被调用的原始方法 Constructor 被调用的原始构造器 Class 当前动态创建的类 MethodHandle MethodType String 动态类的toString()的返回值 int 动态方法的修饰符 |
|
@DefaultCall |
调用默认方法而非super的方法 |
|
@SuperCall |
用于调用父类版本的方法 |
|
@Super |
注入父类型对象,可以是接口,从而调用它的任何方法 |
|
@RuntimeType |
可以用在返回值、参数上,提示ByteBuddy禁用严格的类型检查 |
|
@Empty |
注入参数的类型的默认值 |
|
@StubValue |
注入一个存根值。对于返回引用、void的方法,注入null;对于返回原始类型的方法,注入0 |
|
@FieldValue |
注入被拦截对象的一个字段的值 |
|
@Morph |
类似于@SuperCall,但是允许指定调用参数 |
拦截内部类
内部类用$,即使是私有内部类也可拦截
.or(ElementMatchers.named("org.apache.flink.streaming.api.operators.StreamSourceContexts$SwitchingOnClose"))
Advice内联织入
bytebuddy会内联这些advice代码到目标类,只是一个copy-paste模板,因此默认不会debug到所在的advice类
可以加属性@Advice.OnMethodEnter(inline = true)防止内联,但还是有问题
@Advice.onMethodEnter 拦截入参
@Advice.onMethodExit 拦截出参
public class DataAdvice {
// param1 param2 data1 data2 data3分别代表第1、2个参数,拦截的类的recordId、descr、CLASSNAME成员变量
// #t,#m分别获取类名称和方法名称
@Advice.OnMethodEnter
public static void methodStart(
@Advice.Argument(value=0, readOnly=false) int param1,
@Advice.Argument(value=1, readOnly=false, typing=Assigner.Typing.DYNAMIC) Object param2,
@Advice.FieldValue(value="recordId", readOnly=false) int data1,
@Advice.FieldValue(value="descr", readOnly=false, typing=Assigner.Typing.DYNAMIC) Object data2,
@Advice.FieldValue(value="CLASSNAME", readOnly=false, typing=Assigner.Typing.DYNAMIC) Object data3,
@Advice.Origin("#t") String className,
@Advice.Origin("#m") String methodName){
param1 = 20000;
param2 = "test2";
data1 = 2;
data2 = "newData";
data3 = "DataProducer1";
}
// returnObject代表返回值
@Advice.OnMethodExit
public static void methodEnd(
@Advice.Return(readOnly=false) int returnObject){
returnObject = -2;
}
}
// agent
builder.visit(Advice.to(DataInterceptor.class)
.on(ElementMatchers.isMethod().and(ElementMatchers.named("pushToOperator"))));
// 对同一个插桩点,将Advice与interceptor集成,先调的是interceptor
builder
.method(ElementMatchers.named("translateToPlan"))
.intercept(Advice.to(OperatorExecNodeAdvice.class).wrap(MethodDelegation.to(OperatorExecNodeInterceptor.class)));
// 集成的interceptor传入参数
builder
.method(ElementMatchers.isMethod().and(ElementMatchers.named("collect")).and(ElementMatchers.takesArguments(2)))
.intercept(Advice.to(SourceOutputAdvice.class).wrap(MethodDelegation.to(new SourceOutputInterceptor(producer, topic))))
更多推荐

所有评论(0)