如何通过ByteBuddy初始化DirtyTracker字段并在构造前完成实例化?
解决方案:在构造函数执行前初始化注入字段
要解决你的问题,核心是在父类构造函数执行完成后、目标类构造代码执行前,初始化dirtyTracker字段,这样构造函数里调用的setXXX()方法就能正常访问到该字段。以下是具体实现步骤:
1. 构造函数初始化Advice类
创建一个Advice类,负责在构造函数执行阶段初始化dirtyTracker字段。通过@Advice.OnMethodEnter注解,我们可以在构造函数的业务代码执行前注入初始化逻辑:
public class ConstructorAdvice { @Advice.OnMethodEnter public static void initDirtyTracker(@Advice.This Object instance) throws NoSuchFieldException, IllegalAccessException { // 直接获取注入的dirtyTracker字段并初始化 Field trackerField = instance.getClass().getField("dirtyTracker"); trackerField.set(instance, new DirtyTracker()); } }
2. 修改ByteBuddy增强逻辑
调整你的patchPojo方法,通过visit方式给所有构造函数附加初始化Advice,同时保留原构造函数逻辑;另外优化set方法的拦截逻辑,确保能正确调用原方法并标记脏数据:
protected static <T> Class<? extends T> patchPojo(Class<T> clazz) { return new ByteBuddy() .subclass(clazz) // 注入public的dirtyTracker字段 .defineField("dirtyTracker", DirtyTracker.class, Modifier.PUBLIC) // 给所有构造函数附加初始化逻辑:在构造代码执行前初始化字段 .constructor(ElementMatchers.any()) .visit(Advice.to(ConstructorAdvice.class).onEnter()) // 拦截所有setXXX方法,委托给PojoInterceptor处理 .method(ElementMatchers.nameStartsWith("set")) .intercept(MethodDelegation.to(PojoInterceptor.class)) .make() .load(InjectUtils.class.getClassLoader()) .getLoaded(); }
3. 优化Set方法拦截器
更新PojoInterceptor,通过@SuperCall调用原set方法,同时从方法名提取属性名标记脏数据:
public class PojoInterceptor { @RuntimeType public static void intercept( @This Object instance, @Origin Method method, @SuperCall Runnable superCall ) throws NoSuchFieldException, IllegalAccessException { // 获取已初始化的dirtyTracker DirtyTracker tracker = (DirtyTracker) instance.getClass().getField("dirtyTracker").get(instance); // 从set方法名提取属性名(如setAge -> age) String propName = method.getName().substring(3); propName = Character.toLowerCase(propName.charAt(0)) + propName.substring(1); // 标记属性为脏数据 tracker.markDirty(propName); // 执行原set方法逻辑 superCall.run(); } }
为什么之前的方式失效?
- 用
MethodDelegation.to(...).andThen(SuperMethodCall.INSTANCE)替换构造函数:违反Java构造函数必须先调用父类构造的语法规则,会导致编译错误,且无法正确获取实例。 - 直接用
Advice.to()拦截构造函数但未拿到实例:大概率是因为用了intercept(替换整个构造函数)而非visit(附加逻辑到现有构造函数),且未正确使用@Advice.This注解获取实例对象。
验证效果
创建增强后的Foo实例,构造函数里的setAge(18)会被拦截,dirtyTracker会正确标记age为脏数据:
Class<? extends Foo> patchedFooClass = patchPojo(Foo.class); Foo foo = patchedFooClass.getConstructor().newInstance(); System.out.println(foo.dirtyTracker.isDirty("age")); // 输出true
内容的提问来源于stack exchange,提问作者jun
相关产品推荐
相关产品推荐

