无法为 class 的现有字段添加修改器

Unable to add mutator for an existing field of a class

我正在尝试为现有的 private final 字段添加一个修改器。我可以转换字段修饰符以删除 final 规范并添加访问器方法:

// accessor interface
public interface UniqueIdAccessor {
    Serializable getUniqueId();
}

// mutator interface
public interface UniqueIdMutator {
    void setUniqueId(Serializable uniqueId);
}


...

// fragment of Java agent implementation
return new AgentBuilder.Default()
        .type(hasSuperType(named("org.junit.runner.Description")))
        .transform(new Transformer() {
            @Override
            public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription,
                    ClassLoader classLoader, JavaModule module) {
                return builder.field(named("fUniqueId")).transform(ForField.withModifiers(FieldManifestation.PLAIN))
                              .implement(UniqueIdAccessor.class).intercept(FieldAccessor.ofField("fUniqueId"))
                //            .implement(UniqueIdMutator.class).intercept(FieldAccessor.ofField("fUniqueId"))
                              .implement(Hooked.class);
            }
        })
        .installOn(instrumentation);

...

这里有一个方法,使用反射来检查目标字段的修饰符,并调用访问器来获取字段的值。

private static void injectProxy(Description description) {
    try {
        Field bar = Description.class.getDeclaredField("fUniqueId");
        System.out.println("isFinal: " + ((bar.getModifiers() & Modifier.FINAL) != 0));
    } catch (NoSuchFieldException | SecurityException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    Serializable uniqueId = ((UniqueIdAccessor) description).getUniqueId();
    System.out.println("uniqueId: " + uniqueId);
}

// isFinal: false
// uniqueId: <description-unique-id>

...但是如果我取消注释第二个“实现”表达式以添加增变器,转换就会爆炸:

// isFinal: true
// java.lang.ClassCastException: 
//     class org.junit.runner.Description cannot be cast to class com.nordstrom.automation.junit.UniqueIdAccessor
//     (org.junit.runner.Description and com.nordstrom.automation.junit.UniqueIdAccessor
//     are in unnamed module of loader 'app')

我可以通过反射设置字段值,但这首先违背了使用 Byte Buddy 的目的!

这种方法的问题是字段访问器在修改之前考虑输入类型。 Byte Buddy 禁止这样做,因为它不认为突变是合法的,因为它不知道已删除的修饰符。结果,转换完全失败,您得到了所看到的错误。 (注册一个监听器可以看到这个错误。)

为避免这种情况,您可以使用 FieldAccess(不使用 )实现自定义 Implementation。你可以看看更方便的FieldAccessor看看这是如何实现的,只是你需要放弃有效性检查。

感谢您为我指明正确的方向!我 assemble StackManipulation 对象用这个定义了 mutator 方法:

    final TypeDescription description = TypePool.Default.ofSystemLoader().describe("org.junit.runner.Description").resolve();
    
    final Generic _void_ = TypeDescription.VOID.asGenericType();
    final Generic serializable = TypePool.Default.ofSystemLoader().describe("java.io.Serializable").resolve().asGenericType();
    
    final MethodDescription.Token setUniqueIdToken = new MethodDescription.Token("setUniqueId", Modifier.PUBLIC, _void_, Arrays.asList(serializable));
    final MethodDescription setUniqueId = new MethodDescription.Latent(description, setUniqueIdToken);
    
    final Token fUniqueIdToken = new FieldDescription.Token("fUniqueId", Modifier.PRIVATE, serializable);
    final FieldDescription fUniqueId = new FieldDescription.Latent(description, fUniqueIdToken);
    
    final StackManipulation setUniqueIdImpl = new StackManipulation.Compound(
            MethodVariableAccess.loadThis(),
            MethodVariableAccess.load(setUniqueId.getParameters().get(0)),
            Assigner.DEFAULT.assign(serializable, serializable, Typing.STATIC),
            FieldAccess.forField(fUniqueId).write(),
            MethodReturn.VOID
    );

...然后我将目标 class 转换为:

    return new AgentBuilder.Default()
            .type(hasSuperType(named("org.junit.runner.Description")))
            .transform(new Transformer() {
                @Override
                public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription,
                        ClassLoader classLoader, JavaModule module) {
                    return builder.field(named("fUniqueId")).transform(ForField.withModifiers(FieldManifestation.PLAIN))
                                  .implement(AnnotationsAccessor.class).intercept(FieldAccessor.ofField("fAnnotations"))
                                  .implement(UniqueIdAccessor.class).intercept(FieldAccessor.ofField("fUniqueId"))
                                  .implement(UniqueIdMutator.class).intercept(new Implementation.Simple(setUniqueIdImpl));
                }
            })
            .installOn(instrumentation);

下面是转换中使用的三个接口的定义:

// annotations accessor interface
public interface AnnotationsAccessor {
    Annotation[] annotations();
}

// unique ID accessor interface
public interface UniqueIdAccessor {
    Serializable getUniqueId();
}

// unique ID mutator interface
public interface UniqueIdMutator {
    void setUniqueId(Serializable uniqueId);
}