如何装饰现有 Java 对象的方法?

How to decorate an existing Java object's method?

编辑:我在

描述了我们的解决方案

我有一个 java 对象。它是扩展抽象 class 的许多子 class 之一的实例。我想修改它的一种方法,以便它在调用原始方法之前运行一些额外的代码。我的目标在概念上与 AspectJ 中的切入点相同。

如果我创建原始对象的一些修改版本而不是改变原始对象,那很好。如果解决方案涉及字节码操作也很好。

之前的工作

我考虑过通过 JavaAssist 创建代理。问题是 ProxyFactory 的创建方法需要我提前知道构造函数的输入类型。我不。我可以在不通过 Objenesis 调用构造函数的情况下创建我的对象,但是生成的代理对象对于构造函数设置的任何值都将具有空值。这意味着每当直接引用构造函数设置的值时,我生成的对象的行为将与原始对象不同。

上下文

我们通过 AWS Kinesis Data Analytics 使用 Flink 来转换一些流数据。我们希望在所有 StreamOperator's open() 方法的开头包含一些通用代码,而不必修改每个运算符。一个用例是确保自定义指标代理 运行 在操作员 运行 的每个实例上。

使用 Byte Buddy,您可以创建包装器或 Java 代理,它们都可以实现此目标。如果您在包装器的构造函数调用方面遇到困难 class,但是使用 Byte Buddy 会出现同样的问题,因为任何库都绑定到 JVM 给定的约束。

要创建 Java 代理,请使用 AgentBuilder。然后,您可以使用 type 步骤指定要拦截的所有类型,例如实现特定接口或扩展 class 的所有类型。对于transform,字节好友提供了一个方法decoraction API,叫做Advice,它允许你添加额外的代码,例如:

class MyAdvice {
  @Advice.OnMethodEnter
  static void enter() { System.out.println("Hello"); }
}

来自

builder = builder.visit(Advice.to(MyAdvice.class).on(named("foo")));

例如,您可以在所有名为 "foo" 的方法的开头为您指定的类型打印 hello world。您可以了解更多关于 Java 代理 in the package documentation for the java.instrument package.

Flink 特定的解决方案可能是实现您正在使用的 Flink 运算符的自定义版本。我不相信这会把你带到一个好地方;只是分享想法,以防有帮助。

关于如何实现自定义运算符的文档不多,但是有 Flink Forward talk on this topic

首先,我将在 AWS 上提交功能请求以支持您的用例。那将是最干净的解决方案。

其次,我会避免寻找任何方法来覆盖 open()。由于您处于一个没有太多控制权的环境中,我认为这些方法要么根本不起作用,要么很脆弱,并且会随着环境的更新而中断。

我会在各自的 UDF 方法中进行惰性初始化,当然会在一些常用实用方法中将其分解。

private Counter counter;

@Override
public Integer map(String value) {
    if (counter == null) {
        RuntimeContext ctx = getRuntimeContext();
        counter = ctx.getMetricGroup().counter("outputs");
    }
    counter.inc();
    return Integer.parseInt(value);
}

原始提问者的回答:我们通过为 StreamExecutionEnvironment 创建一个 ByteBuddy 代理解决了这个问题,它拦截了对 getStreamGraph 的调用并将每个节点的 jobVertexClass 重铸(使用反射)到 class 扩展了原始 class 类型,但包括我们的自定义逻辑。因为不同的 classes 需要不同的参数,所以我们使用 Objenesis 在不调用构造函数的情况下实例化代理。为了解决通常在构造函数中设置的私有字段为空的问题,我们使用反射改变所有私有字段的可见性,然后将每个字段值从原始对象复制到代理对象。

我们没有追求 Rafael Winterhalter 提出的代理解决方案,因为它需要能够 运行 每个工作实例上的代理设置代码,这类似于想要启动度量代理的原始问题在每个工人机器上。虽然我在最初的问题中没有说明这一点,但创建代理对象的代码发生在 Flink 作业管理机器上,而不是工作机器上。