Как создать класс с конечными статическими полями в любой библиотеке байт-кода?
Я пытаюсь создать простой класс, который содержит статические поля конечного объекта, используя любую библиотеку байт-кода. Я пробовал BCEL и Byte Buddy, но безуспешно. Класс, который я хочу построить, выглядит следующим образом. Благодарю.
public class ConstructedClass{
public static final MyClass a = new MyClass();
public static final MyClass b = new MyClass();
}
Моя попытка с BCEL:
ClassGen classGen=new ClassGen("org.test.lib.core", "java.lang.Object","core.java", Const.ACC_PUBLIC, null);
classGen.addEmptyConstructor(Const.ACC_PUBLIC);
ConstantPoolGen constantPoolGen=classGen.getConstantPool();
int access_flags = Const.ACC_PUBLIC | Const.ACC_STATIC | Const.ACC_FINAL;
final FieldGen FieldGen=new FieldGen( access_flags,Type.getType(Property.class), "test", constantPoolGen);
//FieldGen.setInitValue(new MyClass());
Моя вторая попытка также с BCEL:
private static final Type[] arg = {Type.getType(MyClass.class)};
InstructionList init = new InstructionList();
InstructionFactory factory=new InstructionFactory(classGen);
//init.append(new PUSH(constantPoolGen, new MyClass()));
init.append(factory.createInvoke(MyClass.class.getName(), "valueOf",
Type.getType(MyClass.class), arg, Const.INVOKESTATIC));
init.append(factory.createPutStatic("org.test.lib.core", "test", Type.getType(Property.class)));
В закомментированных строках нажатие на мой объект не сработало.
1 ответ
Решение
С ByteBuddy Вы можете создать статический блок инициализации, используя ByteCodeAppender. Это приведет к немного другому классу, чем вы хотели, но я думаю достаточно близко:
public class ConstructedClass {
public static final MyClass a;
static {
a = new MyClass();
}
}
Код генерации:
public class ByteBuddyTest {
public static void main(String[] args) throws Exception {
DynamicType.Loaded loaded =
new ByteBuddy()
.subclass(Object.class)
.initializer( new ByteCodeAppender() {
@Override public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext,
MethodDescription instrumentedMethod) {
StackManipulation.Size size = new StackManipulation.Compound(
TypeCreation.of(new TypeDescription.ForLoadedType(MyClass.class)),
Duplication.SINGLE,
MethodInvocation.invoke(new TypeDescription.ForLoadedType(MyClass.class).getDeclaredMethods().filter(ElementMatchers.isDefaultConstructor()).getOnly()),
FieldAccess.forField(implementationContext.getInstrumentedType().getDeclaredFields().filter(ElementMatchers.named("a")).getOnly()).write()
).apply(methodVisitor, implementationContext);
return new Size(size.getMaximalSize(), instrumentedMethod.getStackSize());
}
})
.name("org.test.lib.core.ConstructedClass")
.modifiers(Opcodes.ACC_PUBLIC)
.defineField("a", MyClass.class, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL)
.make()
.load(ByteBuddyTest.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION);
Object obj = loaded.getLoaded().getConstructor().newInstance();
System.out.println(obj.getClass().getField("a").get(obj));
}
public static class MyClass {
public MyClass(String arg) {}
public static MyClass createMyClass(String arg) {
return new MyClass(arg);
}
}
}
Обновление для комментария
Чтобы вызвать статический метод фабрики вместо конструктора, вам просто нужно заменить вызов конструктора:
StackManipulation.Size size = new StackManipulation.Compound(
new TextConstant("test"),
MethodInvocation.invoke(new TypeDescription.ForLoadedType(MyClass.class).getDeclaredMethods().filter(ElementMatchers.named("createMyClass")).getOnly()),
FieldAccess.forField(implementationContext.getInstrumentedType().getDeclaredFields().filter(ElementMatchers.named("a")).getOnly()).write()
).apply(methodVisitor, implementationContext);
Несколько полей
DynamicType.Builder builder = new ByteBuddy().subclass(Object.class);
List<String> fields = Lists.newArrayList("a", "b", "c");
for (String str : fields) {
builder = builder.defineField(str, MyClass.class, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL);
}
DynamicType.Loaded loaded = builder.make().load(ByteBuddyTest.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION);
Object obj = loaded.getLoaded().getConstructor().newInstance();
System.out.println(obj.getClass().getField("a"));
System.out.println(obj.getClass().getField("c"));