如何使用ByteBuddy创建动态代理

9
在Java中,使用InvocationHandler的实现可以创建动态代理。尽管JVM进行了优化,但是使用反射调用方法总会有一些开销。
为了解决这个问题,我尝试使用ByteBuddy在运行时创建代理类,但是文档在这方面看起来不够清晰。
我该如何创建一个MethodCallProxy以将方法调用转发到某个类实例?
编辑:
为了更好地说明我的问题,我提供了一个示例来说明我的目标:
我正在构建一个RPC系统。对于每个方法调用的两端,在JVM下都有定义合同的接口(当调用者/被调用者都在运行时)。
@Contract
interface ISomeService {
    fun someMethod(arg0: String, arg1: SomePojo): PojoResult
}

在调用现场,我注入了一个代理程序来截获所有方法调用并将其转发给被调用方。

ByteBuddy()
    .subclass(Any::class.java)
    .implement(serviceClass)

    // Service contract method delegation
    .method(isDeclaredBy(serviceClass)).intercept(
      MethodDelegation
          .to(ServiceProxyInterceptor())
          .filter(not(isDeclaredBy(Any::class.java)))
    )

    .make()
    .load(this)
    .loaded as Class<T>

最后,在被调用方,我有几个处理程序,每个处理程序对应一个服务方法,负责将调用参数反序列化并将其转发到服务实现。

@Service
class SomeServiceImpl {
    fun someMethod(arg0: String, arg1: SomePojo): PojoResult {
        // ...
    }
}

我可以使用代码生成来解决这个问题,但生成的jar文件可能会变得非常大。因此,我想创建这些处理程序的通用版本,并在每个实例中附加代理,拦截对ISomeService的每个方法调用并将其转发到SomeServiceImpl

1个回答

11

在Byte Buddy中有许多创建代理类的方法。确切的方法取决于您的用例。最简单的方法可能是使用InvocationHandlerAdapter。假设您想为SomeClass创建一个代理,您可以使用以下方式:

Class<? extends SomeClass> proxy = new ByteBuddy()
  .subclass(SomeClass.class)
  .method(ElementMatchers.any())
  .intercept(InvocationHandlerAdapter.of(invocationHandler))
  .make()
  .load(SomeClass.class.getClassLoader());

如果您想创建一个带有委托到不同实例的代理,您需要额外定义一个字段。您可以按照以下说明完成此操作:

Class<? extends SomeClass> proxy = new ByteBuddy()
  .subclass(SomeClass.class)
  .defineField("handler", InvocationHandler.class, Visibility.PUBLIC)
  .method(ElementMatchers.any())
  .intercept(InvocationHandlerAdapter.toField("handler"))
  .make()
  .load(SomeClass.class.getClassLoader());
您可以通过反射或者实现一个setter接口来设置上述字段,例如:
interface HandlerSetter {
  InvocationHandler getHandler();
  void setHandler(InvocationHandler handler);
}

Class<? extends SomeClass> proxy = new ByteBuddy()
  .subclass(SomeClass.class)
  .defineField("handler", InvocationHandler.class, Visibility.PUBLIC)
  .implement(HandlerSetter.class)
  .intercept(FieldAccessor.ofField("handler"))
  .method(ElementMatchers.any())
  .intercept(InvocationHandlerAdapter.toField("handler"))
  .make()
  .load(SomeClass.class.getClassLoader());

现在您可以实例化该类并将其强制转换为接口,以设置处理程序。

除了InvocationHandler之外,还有许多其他创建代理的方法。一种方法是使用更灵活、速度更快并允许您按需调用超级方法的MethodDelegation。还可以使用MethodCallForwarding工具对前向仪器进行应用。您可以在相应类的javadoc中找到详细信息。


当然,针对问题的特定部分,没有理由认为ByteBuddy生成的代理调用InvocationHandler比JRE生成的java.lang.reflect.Proxy调用InvocationHandler更有效率... - Holger
1
在运行时,没有可变参数。只需将上述方法提供一个单一的数组作为参数,但将其转换为对象。我可以重载该方法,但即使如此,调用也会产生歧义。让我想想如何改进这个问题! - Rafael Winterhalter
1
也许我理解错了,但我认为 OP 的描述是关于 withAllArguments() 方法出现问题,而不是 with(Object...) 方法。此外,方法的可变参数特性可以在运行时检测到... - Holger
1
@RafaelWinterhalter 我明白了。我之前也不知道反射膨胀的问题。而且,我也没有意识到装箱和拆箱会有一个合理的开销。如果这成为问题,我可以改变实现方式,生成处理程序的字节码以避免这个问题。 - Carlos Melo
1
这个SO问题https://dev59.com/Lrnoa4cB1Zd3GeqPPF6Y 解释了使用反射InvocationHandler代理和Byte Buddy MethodDelegation的区别。 - FlyingSheep
显示剩余13条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接