如何使用ByteBuddy在字节码中为静态字段赋值?
问题:ByteBuddy Plugin生成类时静态常量赋值失败
需要生成包含如下静态常量定义的类:
public static final Value foo = new Value("bar");
其中Value是自定义内部类。
现有插件代码
public class ExamplePlugin implements Plugin.WithInitialization { public static class Value { private final String foo; public Value(String foo) { this.foo = foo; } } public Map<TypeDescription, byte[]> initialize(ClassFileLocator classFileLocator) { try { DynamicType.Builder<Object> type = new ByteBuddy() .subclass(Object.class) .name("com.example.ExampleGenerated") .defineField("foo", TypeDescription.ForLoadedType.of(Value.class), Visibility.PUBLIC, Ownership.STATIC, FieldManifestation.FINAL) .invokable(MethodDescription::isTypeInitializer) .intercept(FixedValue.reference(new Value("foo"))); try (DynamicType.Unloaded<Object> unloaded = type.make()) { return unloaded.getAllTypes(); } } catch (Exception e) { throw new RuntimeException(e); } } public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassFileLocator classFileLocator) { return builder; } public void close() {} public boolean matches(TypeDescription target) { return false; } }
生成类的反编译结果
public class ExampleGenerated { public static final ExamplePlugin.Value foo; public ExampleGenerated() { } static { ExamplePlugin.Value var10000 = value$25iplg0; } }
可见赋值操作并未作用于foo字段,而是指向了临时变量,导致foo未被正确初始化。
尝试过的无效方案
方案1:拦截方法调用
.intercept(MethodCall.invoke(Callable.class.getMethod("call")).on(() -> new Value("bar"), Callable.class) .setsField(ElementMatchers.named("foo")) .withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC));
该方案生成的静态块会尝试给foo赋值,但lambda对应的Callable实例未被写入字节码,运行时抛出NPE,反编译结果:
static { foo = (ExamplePlugin.Value)invocationTarget$j3n12b1.call(); }
方案2:使用初始化器
.initializer(new LoadedTypeInitializer.ForStaticField("foo", new Value("bar")))
该方案的初始化逻辑是类加载时动态执行的,不会被持久化到字节码中,打包成Jar后无效。
正确实现方式
问题根源在于:
FixedValue.reference是让静态初始化器返回实例,但<clinit>是void方法,返回值会被忽略,且未执行字段赋值操作;- 拦截方法调用用的lambda无法被序列化到字节码;
LoadedTypeInitializer是动态加载逻辑,不写入字节码。
正确的做法是在静态初始化器中直接生成构造Value实例并赋值给foo字段的字节码,修改后的initialize方法如下:
public Map<TypeDescription, byte[]> initialize(ClassFileLocator classFileLocator) { try { DynamicType.Builder<Object> type = new ByteBuddy() .subclass(Object.class) .name("com.example.ExampleGenerated") .defineField("foo", TypeDescription.ForLoadedType.of(Value.class), Visibility.PUBLIC, Ownership.STATIC, FieldManifestation.FINAL) .invokable(MethodDescription::isTypeInitializer) .intercept( // 构造Value实例并赋值给foo字段 MethodCall.construct(Value.class.getConstructor(String.class)) .with("bar") .setsField(ElementMatchers.named("foo")) ); try (DynamicType.Unloaded<Object> unloaded = type.make()) { return unloaded.getAllTypes(); } } catch (Exception e) { throw new RuntimeException(e); } }
生成的类反编译后会得到预期的结果:
public class ExampleGenerated { public static final ExamplePlugin.Value foo; static { foo = new ExamplePlugin.Value("bar"); } public ExampleGenerated() { } }
内容的提问来源于stack exchange,提问作者tytk
相关产品推荐
相关产品推荐

