如何使用代码生成跟踪 POJO 的状态
How to track the state of a POJO using code generation
我们正在寻找一种解决方案,以高效的方式跟踪客户端 POJO 实例的状态。我们期望的是:每次对 POJO 进行更改时,都会使用 setters 来创建此状态。我们创建了一个基于 OGNL 的监视/事件总线,当进行更改时,我们将向我们的事件总线发送适当的 OgnlChangeEvent
。
到目前为止,我们研究了 AspectJ / cglib / 对象图 Diff 解决方案,但它们都占用了太多 CPU 时间。我们当前的解决方案基于 Spring MethodInterceptor
并且每次调用 Getter 方法时我们都会创建一个新的 Proxy
实例。
此时我们开始研究代码生成解决方案,我们偶然发现了 Byte Buddy。这个方向是正确的方法吗?我们可以生成一个新的 Class
来扩展我们的客户端 POJO 状态并在调用 setter 方法之前通知其 OGNL 前缀吗?
Byte Buddy是一个代码生成工具,当然可以实现这样的方案。要创建拦截 setter 的 class,您可以编写如下代码:
new ByteBuddy()
.subclass(UserPojo.class)
.method(ElementMatchers.isSetter())
.intercept(MethodDelegation.to(MyInterceptor.class)
.andThen(SuperMethodCall.INSTANCE)
.make();
你会在哪里编写这样的拦截器:
public class MyInterceptor {
public static void intercept(Object value) {
// business logic comes here
}
}
这样,您可以在每次调用 setter 时添加一些代码,该 setter 在原始代码之前触发。您还可以使用所有基本类型重载拦截方法,以避免对参数进行装箱。字节好友想办法为你做。
但是我很困惑你所说的性能是什么意思。上面的代码对我来说与创建 class 相同:
class UserClass {
String value;
void setValue(String value) {
this.value = value;
}
}
class InstrumentedUserClass extends UserClass {
@Override
void setValue(String value) {
MyInterceptor.intercept(value);
super.setValue(value);
}
}
性能主要受您在 intercept
方法中执行的操作的性能影响。
最后,我不明白 cglib 为什么对你不起作用,但使用 Spring - 它是在 cglib 之上构建的 - 确实有效。我怀疑你的拦截逻辑有问题,你应该调查一下。
我认为性能在很大程度上取决于您使用的字节码检测框架,而取决于您在方法拦截器中执行的操作。最后只有自己测量了才知道。
我对你的用例了解不多,但总的来说我会问自己:
- 我真正需要什么信息?
- 获取该信息的基本数据是什么?
您应该将基本数据收集与数据(信息)的解释分开。通常解释需要更多的时间。基础数据是不能从其他数据中推导出来的数据。例如生日是基本数据,而年龄是从生日派生的。
在方法拦截器中我会
- 只收集class名称、方法名称和可能的参数等基本数据。
- 将此信息发送到某种工作队列
- 让后台工作人员生成信息、记录或保存它。
后台工作者可以,例如解释方法名称以查明它是否是 属性 访问器。通常,您使用 BeanInfo that you get from an Introspector 或至少反射 api 来执行此操作。
我们正在寻找一种解决方案,以高效的方式跟踪客户端 POJO 实例的状态。我们期望的是:每次对 POJO 进行更改时,都会使用 setters 来创建此状态。我们创建了一个基于 OGNL 的监视/事件总线,当进行更改时,我们将向我们的事件总线发送适当的 OgnlChangeEvent
。
到目前为止,我们研究了 AspectJ / cglib / 对象图 Diff 解决方案,但它们都占用了太多 CPU 时间。我们当前的解决方案基于 Spring MethodInterceptor
并且每次调用 Getter 方法时我们都会创建一个新的 Proxy
实例。
此时我们开始研究代码生成解决方案,我们偶然发现了 Byte Buddy。这个方向是正确的方法吗?我们可以生成一个新的 Class
来扩展我们的客户端 POJO 状态并在调用 setter 方法之前通知其 OGNL 前缀吗?
Byte Buddy是一个代码生成工具,当然可以实现这样的方案。要创建拦截 setter 的 class,您可以编写如下代码:
new ByteBuddy()
.subclass(UserPojo.class)
.method(ElementMatchers.isSetter())
.intercept(MethodDelegation.to(MyInterceptor.class)
.andThen(SuperMethodCall.INSTANCE)
.make();
你会在哪里编写这样的拦截器:
public class MyInterceptor {
public static void intercept(Object value) {
// business logic comes here
}
}
这样,您可以在每次调用 setter 时添加一些代码,该 setter 在原始代码之前触发。您还可以使用所有基本类型重载拦截方法,以避免对参数进行装箱。字节好友想办法为你做。
但是我很困惑你所说的性能是什么意思。上面的代码对我来说与创建 class 相同:
class UserClass {
String value;
void setValue(String value) {
this.value = value;
}
}
class InstrumentedUserClass extends UserClass {
@Override
void setValue(String value) {
MyInterceptor.intercept(value);
super.setValue(value);
}
}
性能主要受您在 intercept
方法中执行的操作的性能影响。
最后,我不明白 cglib 为什么对你不起作用,但使用 Spring - 它是在 cglib 之上构建的 - 确实有效。我怀疑你的拦截逻辑有问题,你应该调查一下。
我认为性能在很大程度上取决于您使用的字节码检测框架,而取决于您在方法拦截器中执行的操作。最后只有自己测量了才知道。
我对你的用例了解不多,但总的来说我会问自己:
- 我真正需要什么信息?
- 获取该信息的基本数据是什么?
您应该将基本数据收集与数据(信息)的解释分开。通常解释需要更多的时间。基础数据是不能从其他数据中推导出来的数据。例如生日是基本数据,而年龄是从生日派生的。
在方法拦截器中我会
- 只收集class名称、方法名称和可能的参数等基本数据。
- 将此信息发送到某种工作队列
- 让后台工作人员生成信息、记录或保存它。
后台工作者可以,例如解释方法名称以查明它是否是 属性 访问器。通常,您使用 BeanInfo that you get from an Introspector 或至少反射 api 来执行此操作。