简体   繁体   English

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

[英]How to decorate an existing Java object's method?

EDIT: I've described our solution at https://stackoverflow.com/a/60235242/3236516编辑:我已经在https://stackoverflow.com/a/60235242/3236516描述了我们的解决方案

I have a java object.我有一个 java 对象。 It is an instance of one of many subclasses that extend an abstract class.它是扩展抽象类的许多子类之一的实例。 I would like to modify one of its methods such that it runs some additional code before calling the original method.我想修改它的方法之一,使其在调用原始方法之前运行一些额外的代码。 My goal is conceptually the same as a pointcut in AspectJ.我的目标在概念上与 AspectJ 中的切入点相同。

It is fine if I create some modified version of the original object rather than mutating the original.如果我创建原始对象的一些修改版本而不是改变原始对象,那很好。 It is also fine if the solution involves bytecode manipulation.如果解决方案涉及字节码操作,那也很好。

Prior Work之前的工作

I've considered creating a proxy via JavaAssist.我考虑过通过 JavaAssist 创建代理。 The trouble is that ProxyFactory's create method expects that I know the constructor input types in advance.问题是ProxyFactory的create方法要求我提前知道构造函数的输入类型。 I don't.我不。 I can create my object without calling the constructor via Objenesis, but then the resulting proxy object will have null values for any values set by the constructor.我可以在不通过 Objenesis 调用构造函数的情况下创建我的对象,但是对于构造函数设置的任何值,生成的代理对象将具有空值。 This means my resulting object will behave differently from the original whenever a value set by the constructor is directly referenced.这意味着只要直接引用构造函数设置的值,我的结果对象的行为就会与原始对象不同。

Context语境

We are using Flink via AWS Kinesis Data Analytics to transform some streaming data.我们通过 AWS Kinesis Data Analytics 使用 Flink 来转换一些流数据。 We would like to include some common code at the beginning of all of our StreamOperator's open() methods without having to modify each operator.我们希望在所有StreamOperator 的open() 方法的开头包含一些通用代码,而不必修改每个运算符。 One use case for this is to ensure a custom metrics agent is running on each instance an operator is running on.一个用例是确保自定义指标代理在运营商运行的每个实例上运行。

With Byte Buddy, you can either create a wrapper or a Java agent which can both achieve this goal.使用 Byte Buddy,您可以创建包装器或 Java 代理,它们都可以实现此目标。 If you struggle with constructor invocation of a wrapper class, the same problem would however occure using Byte Buddy as any library is bound to the constraints given by the JVM.如果您在调用包装类的构造函数时遇到困难,那么使用 Byte Buddy 会发生同样的问题,因为任何库都绑定到 JVM 给出的约束。

To create a Java agent, use the AgentBuilder .要创建 Java 代理,请使用AgentBuilder You can then specify all types to intercept using the type step, for example all types that implement a certain interface or extend a class.然后,您可以使用type步骤指定要拦截的所有类型,例如实现某个接口或扩展类的所有类型。 For transform , Byte Buddy offers a method decoraction API called Advice , it allows you to add additional code such as:对于transform ,Byte Buddy 提供了一个称为Advice的方法装饰 API,它允许您添加额外的代码,例如:

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

by经过

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

you can for example print hello world at the start of all methods named "foo" for the types you specified.例如,您可以在所有名为“foo”的方法的开头为您指定的类型打印 hello world。 You can find out more about Java agents in the package documentation for the java.instrument package .您可以java.instrument包的包文档中找到有关 Java 代理的更多信息。

A Flink-specific solution might be to implement custom versions of the Flink operators you are using. Flink 特定的解决方案可能是实现您正在使用的 Flink 运算符的自定义版本。 I'm not convinced this would lead you to a good place;我不相信这会把你带到一个好地方。 just sharing the idea in case it's helpful.只是分享想法,以防万一。

There isn't much documentation on how to implement custom operators, but there has been a Flink Forward talk on this topic .没有太多关于如何实现自定义操作符的文档,但是Flink Forward 上有一个关于这个主题的演讲

First of all, I'd file a feature request on AWS to support your use case.首先,我会在 AWS 上提交功能请求以支持您的用例。 That would be the cleanest solution.那将是最干净的解决方案。

Second, I would refrain from finding any way to overwrite open() .其次,我不会找到任何覆盖open() Since you are in an environment where you don't have much control, I'd imagine approaches either do not work at all or are fragile and break with an update of the environment.由于您处于一个没有太多控制权的环境中,我认为这些方法要么根本不起作用,要么很脆弱,并且会随着环境的更新而中断。

I would do a lazy initialization of in the respective UDF methods and of course factor that out in some common utility method.我会在各自的 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);
}

Answer from original asker: We solved the problem by creating a ByteBuddy proxy for StreamExecutionEnvironment that intercepted calls to getStreamGraph and recast (using reflection) each node's jobVertexClass to a class that extended the original class type, but included our custom logic.来自原始提问者的回答:我们通过为 StreamExecutionEnvironment 创建一个 ByteBuddy 代理来解决这个问题,该代理拦截对 getStreamGraph 的调用并将每个节点的 jobVertexClass 重铸(使用反射)到一个扩展原始类类型的类,但包括我们的自定义逻辑。 Because different classes require different parameters, we instantiated the proxy without calling a constructor by using Objenesis.由于不同的类需要不同的参数,我们使用 Objenesis 来实例化代理,无需调用构造函数。 To solve the problem of private fields normally set in the constructor being left null, we used reflection to alter the visibility of all private fields and then copied every field value from the original object to the proxy object.为了解决通常在构造函数中设置的私有字段为空的问题,我们使用反射来改变所有私有字段的可见性,然后将每个字段值从原始对象复制到代理对象。

We did not pursue the agent solution proposed by Rafael Winterhalter because it requires the ability to run the agent setup code on every worker instance, which is analogous to the original problem of wanting to start a metrics agent on each worker machine.我们没有追求 Rafael Winterhalter 提出的代理解决方案,因为它需要能够在每个工作实例上运行代理设置代码,这类似于想要在每个工作机器上启动度量代理的原始问题。 Though I did not state this in my original question, the code creating the proxy objects occurs on the Flink job management machine, not the worker machines.虽然我在最初的问题中没有说明这一点,但创建代理对象的代码发生在 Flink 作业管理机器上,而不是工作机器上。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM