如何使用 Byte Buddy 将方法正确添加到现有 class

How to correctly add a method to an existing class with Byte Buddy

我面临与此处描述的问题相同的问题 ,但是我不确定如何根据我的用例调整解决方案:

我试图通过将方法实现委托给 拦截器。 ActiveRecord基数class定义如下:

public class ActiveRecord {

   private Long id;

   public Long getId() {
       return id;
   }

   public void setId(Long id) {
       this.id = id;
   }

   private static IllegalStateException implementationMissing() {
      return new IllegalStateException(
            "This method must be overridden in subclasses");
  }

  public static Long count(){
      throw implementationMissing();
  }

  public void save(){
    throw implementationMissing();
  }
// extra methods omitted
}

A child class 将按如下方式扩展活动记录:

class MapText extends ActiveRecord{
    private String text;
    private String description;

    private double wgs84Latitude;
    private double wgs84Longitude;
    // getters and setters omitted

}

使用 Byte Buddy,我尝试将计数和保存方法委托给拦截器 class,如下所示:

@Test
void testRedefine(){
    ByteBuddyAgent.install();
    new ByteBuddy().redefine(MapText.class)
            .defineMethod("save", void.class, Visibility.PUBLIC)
            .intercept(MethodDelegation.to(ActiveRecordInterceptor.class))
            .defineMethod("count", Long.class, Visibility.PUBLIC)
            .intercept(MethodDelegation.to(ActiveRecordInterceptor.class))
            .make()
            .load(MapText.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
    
    MapText mapText = new MapText();
    // set properties
    mapText.save();
    MapText.count();
}

生成以下异常:

java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method

如果我在 MapText 中为 save()count() 添加空的“占位符”方法,那么一切正常。

我应该如何调整我的代码以使委托工作而不需要子class中的空占位符方法?

编辑:根据反馈更改代码以使用 AgentBuilder API

 @Test
 void testRedefine(){
 ByteBuddyAgent.install();
    new AgentBuilder.Default()
            .disableClassFormatChanges()
            .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
            .type(ElementMatchers.named("pkg.MapText"))
            .transform(new AgentBuilder.Transformer() {
                @Override
                public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {
                    return builder.defineMethod("save", void.class, Visibility.PUBLIC)
                            .intercept(MethodDelegation.to(ActiveRecordInterceptor.class));
                }
            }).with(new ListenerImpl()).installOnByteBuddyAgent();
    CallTextSave callTextSave =  new CallTextSave();
    callTextSave.save();
    }    

CallTextSave封装了MapTextclass并调用保存方法。不幸的是MapText.save()没有被拦截。

public class CallTextSave {

   public void save(){
       MapText text = new MapText();
       text.save(); // Method not intercepted

   }
}

如果您想以这种方式更改代码,则需要在首次加载之前执行此操作。您可以通过使用 AgentBuilder API 定义 Java 代理来实现。您必须避免在代理代码中引用加载的 class,而应使用 named 作为将字符串名称作为参数的匹配器。

或者,您可以通过使用 TypePool.Default 解析 class 来重新定义 main 方法中的 class。同样,按名称解析 TypeDescription 并避免加载它。此外,将实际代码移动到不同的 class,因为 JVM 验证器将加载有问题的 class。

后一种方法只有在您控制应用程序的生命周期时才有可能。