I'm having difficulty understanding the documentation for Byte Buddy. To help me learn the API I would like to generate the byte code equivalent of this Java:
public final class GeneratedByByteBuddy {
private final int a;
public GeneratedByByteBuddy(final int a) {
this.a = a;
}
}
I've had difficulty working out the right way to use Instrumentation
to create the field assignment.
You are creating a class with customized byte code. For this, you cannot use a built-in Instrumentation
but you need to write your own instrumentation which creates the particular byte code for your constructor. This Instrumentation
can of course be implemented for Byte Buddy, but if you know all the specifics about your generated class, it would be better to compile this class using javac. I assume you want to learn about the API and this is not the actual class you are trying to create.
For your example class, you would need to implement the equivalent of the desugared version of the constructor. Constructors are only methods to the Java runtime but they follow specific semantics which are enforced by the JVM's verifier. The desugared constructor looks like:
public GeneratedByByteBuddy(int a) {
super();
this.a = a;
return;
}
The Byte Buddy implementation of this very same class looks like this:
new ByteBuddy()
.subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
.name("my.company.GeneratedByByteBuddy")
.defineField("a", int.class, Visibility.PRIVATE, FieldManifestation.FINAL)
.defineConstructor(Arrays.<Class<?>>asList(int.class), Visibility.PUBLIC)
.intercept(new Instrumentation() {
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
@Override
public ByteCodeAppender appender(final Target instrumentationTarget) {
return new ByteCodeAppender() {
@Override
public boolean appendsCode() {
return true;
}
@Override
public Size apply(MethodVisitor methodVisitor,
Context instrumentationContext,
MethodDescription instrumentedMethod) {
StackManipulation.Size size = new StackManipulation.Compound(
MethodVariableAccess.REFERENCE.loadFromIndex(0),
MethodInvocation.invoke(new TypeDescription.ForLoadedType(Object.class)
.getDeclaredMethods()
.filter(isConstructor().and(takesArguments(0))).getOnly()),
MethodVariableAccess.REFERENCE.loadFromIndex(0),
MethodVariableAccess.INTEGER.loadFromIndex(1),
FieldAccess.forField(instrumentationTarget.getTypeDescription()
.getDeclaredFields()
.named("a"))
.putter(),
MethodReturn.VOID
).apply(methodVisitor, instrumentationContext);
return new Size(size.getMaximalSize(), instrumentedMethod.getStackSize());
}
};
}
})
.make()
.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION);
Each method implementation is implemented by an Instrumentation
which is able to modify the created type by adding fields or methods. This is not necessary for your class. Then, it emits a ByteCodeAppender
which is used queried for writing byte code instructions.