4

我正在尝试为具有字节伙伴的字段创建“自定义”设置方法。Buddy 自己的机制允许非常容易地实现标准的 setter/getter 方法,但是,我正在寻找一种优雅的方法来使用一些额外的逻辑来扩展 setter。

为了简化示例,假设我们有一个类 A,它有一个方法 setChanged(String)。目标是做一个A的子类,添加一个具有相应访问方法的字段。问题是,我想从每个添加的 setter 方法中调用 setChanged("fieldName") 。

public void setName(String name)
{
  setChanged("name");
  this.name = name;
}

对于“正常”的 setter 方法,字节 byddy 实现将是:

new ByteBuddy()
  .subclass(A.class)
  .name("B")
  .defineField("name", Integer.TYPE, Visibility.PUBLIC)
   // args is a ArrayList<Class<?>>
  .defineMethod(getSetterName(name), Void.TYPE, args, Visibility.PUBLIC) 
  .intercept( FieldAccessor.ofField(name) )

我追求的字节码如下所示:

L0
 ALOAD 0       // Loads the this reference onto the operand stack
 ILOAD 1       // Loads the integer value of the local variable 1 (first method arg)
 PUTFIELD package/B.name : I // stores the value to the field
L1
 ALOAD 0
 LDC "name"
 INVOKEVIRTUAL package/A.setChanged (Ljava/lang/String;)V
 RETURN

我的问题是:有没有办法在这种情况下重用 FieldAccessor ?

4

1 回答 1

3

到今天为止,您需要定义一个自定义Instrumentation来完成这样的自定义工作。正如评论中所指出的,然后您可以使用 anInstrumentation.Compound将新行为添加到例如FieldAccessor.ofBeanProperty(),从而重用字段访问器代码。

为了添加自定义代码,Byte Buddy 知道不同的抽象级别:

  1. Instrumentation: 定义方法是如何实现的。仪器能够定义实现方法所需的附加字段、方法或静态初始化程序块。此外,它确定是否要为一个类型定义一个方法。
  2. AByteCodeAppender由 an 发出Instrumentation并确定定义的方法是否是抽象的,如果实现了方法,则确定方法的字节码。
  3. AStackManipulation是对操作数堆栈大小具有给定影响的字节码指令。堆栈操作是为实现非抽象方法而组成的。

为了以字节码调用方法,您需要将所有参数(包括this)加载到堆栈中,并在放置所有这些参数后调用该方法。这可以按如下方式完成:

  1. 将引用加载this到堆栈上MethodVariableAccess.REFERENCE.loadFromIndex(0)
  2. 将一个字符串加载到描述正在访问的字段的堆栈上。这可以从ByteCodeAppender作为参数提供给 a 的方法名称派生而来。使用 a TextConstant,然后可以将名称放在堆栈上。
  3. 然后可以使用 a 调用方法MethodInvocation,其中setChanged可以从创建的检测类型中提取方法,该类型作为参数TypeDescription提供给。Instrumentation

当然,这不是很漂亮,Byte Buddy 的愿望是对用户隐藏这个字节码级别的 API,并用 DSL 或纯 Java 表达任何东西。因此,您可能会很高兴听到我目前正在使用 Byte Buddy 0.4 版,它带有一些您可以使用的功能。对于您的示例,您可以使用 Byte Buddy 的瑞士军刀的扩展形式实现自定义设置器,MethodDelegation. 方法委托允许您通过使用注释委托对任何 Java 方法的调用来实现方法。

假设您的 bean 实现了一种类型:

interface Changeable {
  void setChanged(String field);
}

您可以使用以下方法拦截方法调用:

class Interceptor {
  static void intercept(@This Changeable thiz, @Origin Method method) {
    thiz.setChanged(method.getName());
  }
}

使用方法委托,Byte Buddy 将始终在调用方法时调用拦截器。拦截器方法传递了描述特定拦截上下文的参数。上面,this传递了引用和被拦截的方法。

当然,我们仍然缺少该字段的实际设置。但是,使用 Byte Buddy 0.4,您现在可以Instrumentation像下面这样简单地创建一个新的:

MethodDelegation.to(Interceptor.class).andThen(FieldAccessor.ofBeanProperty())

使用这个委托,Byte Buddy 首先调用 intercepor(然后丢弃任何潜在的返回值),最后将Instrumentation作为参数传递给andThen方法的那个应用。

于 2014-11-03T00:18:11.707 回答