Java字节码:带字节伙伴的自定义setter / getter

时间:2014-10-29 10:29:26

标签: java bytecode bytecode-manipulation byte-buddy

我正在努力创建一个" custom"具有字节伙伴的字段的setter方法。 Buddy自己的机制允许非常容易地实现标准的setter / getter方法,但是,我正在寻找一种优雅的方法来扩展setter并增加一些逻辑。

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

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

对于"正常" setter方法,byte 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) )

Bytecode我看起来像这样:

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?

1 个答案:

答案 0 :(得分:3)

截至今天,您需要定义自定义Instrumentation来执行此类自定义作业。正如评论中指出的那样,您可以使用Instrumentation.Compound将新行为添加到例如FieldAccessor.ofBeanProperty()之前,从而重用字段访问器代码。

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

  1. Instrumentation:定义方法的实现方式。检测能够定义实现方法所需的其他字段,方法或静态初始化块。此外,它确定是否要为类型定义方法。
  2. ByteCodeAppender发出Instrumentation,确定定义的方法是否为抽象方法,如果实现方法,则确定方法的字节代码。
  3. StackManipulation是一个字节代码指令,对操作数堆栈的大小有一定的影响。堆栈操作是为实现非抽象方法而组建的。
  4. 为了在字节代码中调用方法,您需要将所有参数(包括this)加载到堆栈中,并在放置所有这些参数后调用该方法。这可以按如下方式完成:

    1. this
    2. MethodVariableAccess.REFERENCE.loadFromIndex(0)引用加载到堆栈中
    3. 将一个字符串加载到描述访问字段的堆栈上。这可以从方法的名称派生,该名称作为参数提供给ByteCodeAppender。使用TextConstant,可以将名称放在堆栈上。
    4. 然后可以使用MethodInvocation调用方法,其中setChanged方法可以从创建的检测类型TypeDescription中提取以Instrumentation作为论据。
    5. 当然,这不是很漂亮,而且Byte Buddy希望从用户隐藏这个字节代码级API并在DSL或普通Java中表达任何内容。因此,您可能会很高兴听到我正在使用Byte Buddy版本0.4,它附带了一些您可以使用的功能。对于您的示例,您可以使用Byte Buddy的瑞士军刀MethodDelegation的扩展形式来实现自定义setter。方法委派允许您通过使用注释委托对任何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首先调用拦截器(然后删除任何潜在的返回值),最后将作为参数传递的Instrumentation应用于andThen方法。