运行时动态快速生成类,快速调用生成的类,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,都可定义要拦截的类后再增强对应的方法

动态增强的三种方式

  1. subclass 新建一个子类
  2. rebase 保留原方法并新增一个名字加了”$original$随机数“后缀的方法
  3. 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))))

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐