`Advice.WithCustomMapping` 绑定动态添加的字段

问题描述

使用 byte-buddy,我想我需要使用 Advice.WithCustomMapping 来引用我根据某些运行时逻辑选择的任意字段。

但是,我对如何创建对作为检测的一部分添加的字段的引用有点迷茫?

AgentBuilder ab = new AgentBuilder.Default();
...
ab = ab.type(named(type))
       .transform((builder,td,_cl,_m) ->
          builder
             .defineField("$lat$",LatencyEvent.class,Visibility.PUBLIC) // new field; I want to create multiple
             .visit(Advice.withCustomMapping()
             .bind(Lat.class,td.getDeclaredFields().filter(named("$lat$")).getonly()) // ok,here I want to reference the newly added field
             .to(EndLatAdvice.class)
             .on(isMethod().and(named(until)))) // pick a target method
...
private static class EndLatAdvice {
      @Advice.OnMethodEnter
      static void enter(@Lat LatencyEvent l) { // hoping to have access to the newly added field
...
}

此外,当我们在此时,我注意到对于 Advice.FieldValue,我们需要指定 readOnly = false,如果我们打算写回该字段。 WithCustomMapping 是如何完成的(假设这是用于此目的的正确工具)。

解决方法

您将提供一个 Advice.OffsetMapping 实例,其中您拥有所有字段作为参数值提供的检测类型。

从该映射中,您将为此字段返回一个 Advice.OffsetMapping.Target.ForField.ReadWrite 实例,除非您有自定义应用。

,

好的,我想我想出了两种方法。很高兴听到哪一个被认为是“规范的”。

  1. 创建另一个仅创建字段的 AgentBuilder
AgentBuilder addField = new AgentBuilder.Default();
AgentBuilder ab = new AgentBuilder.Default();
...
addField = addField.type(named(type))
       .transform((builder,td,_cl,_m) ->
          builder
             .defineField("$lat$",LatencyEvent.class,Visibility.PUBLIC))
ab = ab.type(named(type))
       .transform((builder,_m) ->
          builder
             .visit(Advice.withCustomMapping()
             .bind(Lat.class,td.getDeclaredFields().filter(named("$lat$")).getOnly()) // ok,this one now finds the field
             .to(EndLatAdvice.class)
             .on(isMethod().and(named(until)))) // pick a target method
...
     addField.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
        .ignore(none())
        .installOn(inst);

      ab.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
        .ignore(none())
        .installOn(inst);

现在有两个代理,一个是三个简单的创建字段,第二个是重新定义相同的类,运行的时候可以看到addField创建的字段。不知道执行顺序有没有保证。

  1. 创建对字段的 Latent 引用,而不是按名称查找。
TypeDescription.Generic latField = new TypeDescription.Generic.OfNonGenericType.ForLoadedType(LatencyEvent.class);
...
ab = ab.type(named(type))
       .transform((builder,Visibility.PUBLIC) // new field; I want to create multiple
             .visit(Advice.withCustomMapping()
             .bind(Lat.class,new FieldDescription.Latent(td,"$lat$",1,latField,List.of())) // ok,we can reference the field by name
             .to(EndLatAdvice.class)
             .on(isMethod().and(named(until)))) // pick a target method

这个只适用于一个代理。

但这是只读引用 - 不能写回字段。

为了写,需要参考略有不同:

  1. 与上面的 (2) 相同,除了 bindLat.class 注释应该是:
...
.bind(Lat.class,new Advice.OffsetMapping.ForField.Unresolved.WithImplicitType(latField,false,Assigner.Typing.STATIC,"$lat$"))
...

所以,仍然是 bind,只需要一个 ForField...WithImplicitType 而不是 Latent 引用。