且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2022-12-10 11:47:32

截至今天,你需要定义一个自定义 Instrumentation 来做这样的自定义工作。正如评论中所指出的那样,您可以使用 Instrumentation.Compound 来预先添加新行为,例如 FieldAccessor.ofBeanProperty(),从而重用字段访问器代码。

As of today, you would need to define a custom Instrumentation to do such a custom job. As pointed out in the comments, you can then use an Instrumentation.Compound to prepend the new behavior to for example FieldAccessor.ofBeanProperty() and thus reuse the field accessor code.

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

In order to add custom code, Byte Buddy knows different abstraction levels:


  1. Instrumentation :定义方法的实现方式。检测能够定义实现方法所需的其他字段,方法或静态初始化块。此外,它确定是否要为类型定义方法。

  2. A ByteCodeAppender 由 Instrumentation 并确定定义的方法是否是抽象的,如果实现了方法,则确定方法的字节代码。

  3. A StackManipulation 是一个字节代码指令,对操作数堆栈的大小有一定的影响。堆栈操作是为实现非抽象方法而组成的。

  1. Instrumentation: Defines how a method is implemented. An instrumentation is able to define additional fields, methods or static initializer blocks that are required to implement a method. Furthermore, it determines if a method is to be defined for a type at all.
  2. A ByteCodeAppender is emitted by an Instrumentation and determines if a defined method is abstract and a method's byte code if a method is implemented.
  3. A StackManipulation is a byte code instruction with a given impact on an operand stack's size. Stack manipulations are composed for implementing a non-abstract method.

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

In order to call a method in byte code, you need to load all arguments (including this) onto the stack and call the method after placing all these arguments. This can be done as follows:


  1. 引用加载到堆栈中 MethodVariableAccess.REFERENCE.loadFromIndex(0)

  2. 将一个字符串加载到描述访问字段的堆栈上。这可以从方法的名称派生,该名称作为参数提供给 ByteCodeAppender 。使用 TextConstant ,然后可以在堆栈上放置一个名称。

  3. 然后可以使用 MethodInvocation 其中 setChanged 方法可以从创建的检测类型 TypeDescription 作为参数提供给 Instrumentation

  1. Load the this reference onto the stack by MethodVariableAccess.REFERENCE.loadFromIndex(0).
  2. Load a string onto the stack that describes the field that is access. This can be derived from the method's name which is given to a ByteCodeAppender as an argument. Using a TextConstant, a name can then be placed on the stack.
  3. A method can then be invoked by using a MethodInvocation where the setChanged method can be extracted from the created instrumented type's TypeDescription which is given to the Instrumentation as an argument.

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

Of course, this is not very pretty and it is Byte Buddy's aspiration to hide this byte code level API from a user and to express anything in a DSL or in plain Java. You might therefore be happy to hear that I am currently working with Byte Buddy version 0.4 which comes with some features that you can use for this. For your example, you can implement the custom setter using an extended form of Byte Buddy's Swiss army knife, the MethodDelegation. A method delegation allows you to implement a method by delegating a call to any Java method using annotations.

假设您的bean实现了类型:

Assuming that your beans implement a type:

interface Changeable {
  void setChanged(String field);
}

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

you can intercept a method call using:

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

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

Using a method delegation, Byte Buddy will always call the interceptor when a method is invoked. The interceptor method is passed arguments that describe the context of a specific interception. Above, the this reference and the method that is intercepted are passed.

当然,我们仍然缺少该字段的实际设置。但是,使用Byte Buddy 0.4,您现在可以创建一个新的 Instrumentation ,如下所示:

Of course we are still missing the actual setting of the field. However, with Byte Buddy 0.4, you can now create a new Instrumentation as easy as follows:

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

使用此委托,Byte Buddy首先调用拦截器(然后删除任何潜在的返回值),最后应用作为参数传递的 Instrumentation 然后方法。

With this delegation, Byte Buddy first calls the intercepor (then drops any potential return value) and finally applies the Instrumentation that is passed as an argument to the andThen method.