如何引用 class 中我当前使用 Byte Buddy 定义的字段?
How do I refer to a field in the class which I am currently defining with Byte Buddy?
我正在尝试使用 Byte Buddy 将 JSON 模式编译为 JavaBeans。我有 class、字段和 getter/setter 代工作。我也想生成 toString/equals/hashCode,但似乎这样做需要为 class 上的字段获取 FieldDescription
我正在定义,但我没有看到任何方式做到这一点。可能吗?还是我的处理方式完全错误?
我的代码的基本部分:
public Class<?> createClass(final String className) {
DynamicType.Builder<Object> builder = new ByteBuddy()
.subclass(Object.class)
.name(className);
// create fields and accessor methods
for(final Map.Entry<String, Type> field : this.fields.entrySet()) {
final String fieldName = field.getKey();
Type fieldValue = field.getValue();
if (fieldValue instanceof ClassDescription) {
// recursively generate classes as needed
fieldValue = ((ClassDescription) fieldValue).createClass(fieldName);
}
builder = builder
// field
.defineField(fieldName, fieldValue, Visibility.PRIVATE);
// getter
.defineMethod(getterName(fieldName), fieldValue, Visibility.PUBLIC)
.intercept(FieldAccessor.ofBeanProperty())
// setter
.defineMethod(setterName(fieldName), Void.TYPE, Visibility.PUBLIC)
.withParameter(fieldValue)
.intercept(FieldAccessor.ofBeanProperty());
}
// TODO: Create toString/hashCode/equals
// builder = builder
// .defineMethod("toString", String.class, Visibility.PUBLIC)
// .intercept(new ToStringImplementation(fieldDescriptions));
final Class<?> type = builder
.make()
.load(this.getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
return type;
}
public Implementation makeToString(final LinkedHashMap<String, FieldDescription> fields) {
final ArrayList<StackManipulation> ops = new ArrayList<>();
try {
final TypeDescription stringBuilderDesc = new TypeDescription.ForLoadedType(StringBuilder.class);
final MethodDescription sbAppend = new MethodDescription.ForLoadedMethod(
StringBuilder.class.getDeclaredMethod("append", Object.class));
final MethodDescription sbToString = new MethodDescription.ForLoadedMethod(
StringBuilder.class.getDeclaredMethod("toString"));
// create the StringBuilder
ops.add(MethodInvocation.invoke(
new MethodDescription.ForLoadedConstructor(StringBuilder.class.getConstructor()))
);
// StringBuilder::append returns the StringBuilder, so we don't need to
// save the reference returned from the 'new'
for(final Map.Entry<String, FieldDescription> field : fields.entrySet()) {
ops.add(FieldAccess.forField(field.getValue()).read());
ops.add(MethodInvocation.invoke(sbAppend));
}
// call StringBuilder::toString
ops.add(MethodInvocation.invoke(sbToString).virtual(stringBuilderDesc));
// return the toString value
ops.add(MethodReturn.of(TypeDescription.STRING));
} catch (final NoSuchMethodException | SecurityException e) {
throw new RuntimeException(e);
}
return new Implementation.Simple(ops.toArray(EMPTY_STACKMANIPULATION_ARRAY));
}
我想出了一个基于 this answer 的解决方案。这是一个简化版本。
此示例将采用一个 class,其中包含一个名为 name
的字符串字段,并生成一个 toString
,这将导致类似于 MyGeneratedClass[name=Tom]
的输出。
public static class ToStringImplementation implements Implementation {
public static final TypeDescription SB_TYPE;
public static final MethodDescription SB_CONSTRUCTOR_DEFAULT;
public static final MethodDescription SB_APPEND_STRING;
public static final MethodDescription SB_TO_STRING;
static {
try {
SB_TYPE = new TypeDescription.ForLoadedType(StringBuilder.class);
SB_CONSTRUCTOR_DEFAULT = new MethodDescription.ForLoadedConstructor(StringBuilder.class.getConstructor());
SB_APPEND_STRING = new MethodDescription.ForLoadedMethod(StringBuilder.class.getDeclaredMethod("append", String.class));
SB_TO_STRING = new MethodDescription.ForLoadedMethod(StringBuilder.class.getDeclaredMethod("toString"));
}
catch (final NoSuchMethodException | SecurityException e) {
throw new RuntimeException(e);
}
}
@Override
public InstrumentedType prepare(final InstrumentedType instrumentedType) {
return instrumentedType;
}
@Override
public ByteCodeAppender appender(final Target implementationTarget) {
final TypeDescription thisType = implementationTarget.getInstrumentedType();
return new ByteCodeAppender.Simple(Arrays.asList(
// allocate the StringBuilder
TypeCreation.of(SB_TYPE),
// constructor doesn't return a reference to the object, so need to save a copy
Duplication.of(SB_TYPE),
// invoke the constructor
MethodInvocation.invoke(SB_CONSTRUCTOR_DEFAULT),
// opening portion of toString output
new TextConstant(thisType.getName() + "["),
MethodInvocation.invoke(SB_APPEND_STRING),
// field label
new TextConstant("name="),
MethodInvocation.invoke(SB_APPEND_STRING),
// field value
// virtual call first param is always "this" reference
MethodVariableAccess.loadThis(),
// first param to append is the field value
FieldAccess.forField(thisType.getDeclaredFields()
.filter(ElementMatchers.named("name"))
.getOnly()
).read(),
// invoke append(String), since name is a String-type field
MethodInvocation.invoke(SB_APPEND_STRING),
// closing portion of toString output
new TextConstant("]"),
MethodInvocation.invoke(SB_APPEND_STRING),
// call toString and return the result
MethodInvocation.invoke(SB_TO_STRING),
MethodReturn.of(TypeDescription.STRING)
));
}
}
像这样应用它
builder
.method(ElementMatchers.named("toString"))
.intercept(new ToStringImplementation());
我正在尝试使用 Byte Buddy 将 JSON 模式编译为 JavaBeans。我有 class、字段和 getter/setter 代工作。我也想生成 toString/equals/hashCode,但似乎这样做需要为 class 上的字段获取 FieldDescription
我正在定义,但我没有看到任何方式做到这一点。可能吗?还是我的处理方式完全错误?
我的代码的基本部分:
public Class<?> createClass(final String className) {
DynamicType.Builder<Object> builder = new ByteBuddy()
.subclass(Object.class)
.name(className);
// create fields and accessor methods
for(final Map.Entry<String, Type> field : this.fields.entrySet()) {
final String fieldName = field.getKey();
Type fieldValue = field.getValue();
if (fieldValue instanceof ClassDescription) {
// recursively generate classes as needed
fieldValue = ((ClassDescription) fieldValue).createClass(fieldName);
}
builder = builder
// field
.defineField(fieldName, fieldValue, Visibility.PRIVATE);
// getter
.defineMethod(getterName(fieldName), fieldValue, Visibility.PUBLIC)
.intercept(FieldAccessor.ofBeanProperty())
// setter
.defineMethod(setterName(fieldName), Void.TYPE, Visibility.PUBLIC)
.withParameter(fieldValue)
.intercept(FieldAccessor.ofBeanProperty());
}
// TODO: Create toString/hashCode/equals
// builder = builder
// .defineMethod("toString", String.class, Visibility.PUBLIC)
// .intercept(new ToStringImplementation(fieldDescriptions));
final Class<?> type = builder
.make()
.load(this.getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
return type;
}
public Implementation makeToString(final LinkedHashMap<String, FieldDescription> fields) {
final ArrayList<StackManipulation> ops = new ArrayList<>();
try {
final TypeDescription stringBuilderDesc = new TypeDescription.ForLoadedType(StringBuilder.class);
final MethodDescription sbAppend = new MethodDescription.ForLoadedMethod(
StringBuilder.class.getDeclaredMethod("append", Object.class));
final MethodDescription sbToString = new MethodDescription.ForLoadedMethod(
StringBuilder.class.getDeclaredMethod("toString"));
// create the StringBuilder
ops.add(MethodInvocation.invoke(
new MethodDescription.ForLoadedConstructor(StringBuilder.class.getConstructor()))
);
// StringBuilder::append returns the StringBuilder, so we don't need to
// save the reference returned from the 'new'
for(final Map.Entry<String, FieldDescription> field : fields.entrySet()) {
ops.add(FieldAccess.forField(field.getValue()).read());
ops.add(MethodInvocation.invoke(sbAppend));
}
// call StringBuilder::toString
ops.add(MethodInvocation.invoke(sbToString).virtual(stringBuilderDesc));
// return the toString value
ops.add(MethodReturn.of(TypeDescription.STRING));
} catch (final NoSuchMethodException | SecurityException e) {
throw new RuntimeException(e);
}
return new Implementation.Simple(ops.toArray(EMPTY_STACKMANIPULATION_ARRAY));
}
我想出了一个基于 this answer 的解决方案。这是一个简化版本。
此示例将采用一个 class,其中包含一个名为 name
的字符串字段,并生成一个 toString
,这将导致类似于 MyGeneratedClass[name=Tom]
的输出。
public static class ToStringImplementation implements Implementation {
public static final TypeDescription SB_TYPE;
public static final MethodDescription SB_CONSTRUCTOR_DEFAULT;
public static final MethodDescription SB_APPEND_STRING;
public static final MethodDescription SB_TO_STRING;
static {
try {
SB_TYPE = new TypeDescription.ForLoadedType(StringBuilder.class);
SB_CONSTRUCTOR_DEFAULT = new MethodDescription.ForLoadedConstructor(StringBuilder.class.getConstructor());
SB_APPEND_STRING = new MethodDescription.ForLoadedMethod(StringBuilder.class.getDeclaredMethod("append", String.class));
SB_TO_STRING = new MethodDescription.ForLoadedMethod(StringBuilder.class.getDeclaredMethod("toString"));
}
catch (final NoSuchMethodException | SecurityException e) {
throw new RuntimeException(e);
}
}
@Override
public InstrumentedType prepare(final InstrumentedType instrumentedType) {
return instrumentedType;
}
@Override
public ByteCodeAppender appender(final Target implementationTarget) {
final TypeDescription thisType = implementationTarget.getInstrumentedType();
return new ByteCodeAppender.Simple(Arrays.asList(
// allocate the StringBuilder
TypeCreation.of(SB_TYPE),
// constructor doesn't return a reference to the object, so need to save a copy
Duplication.of(SB_TYPE),
// invoke the constructor
MethodInvocation.invoke(SB_CONSTRUCTOR_DEFAULT),
// opening portion of toString output
new TextConstant(thisType.getName() + "["),
MethodInvocation.invoke(SB_APPEND_STRING),
// field label
new TextConstant("name="),
MethodInvocation.invoke(SB_APPEND_STRING),
// field value
// virtual call first param is always "this" reference
MethodVariableAccess.loadThis(),
// first param to append is the field value
FieldAccess.forField(thisType.getDeclaredFields()
.filter(ElementMatchers.named("name"))
.getOnly()
).read(),
// invoke append(String), since name is a String-type field
MethodInvocation.invoke(SB_APPEND_STRING),
// closing portion of toString output
new TextConstant("]"),
MethodInvocation.invoke(SB_APPEND_STRING),
// call toString and return the result
MethodInvocation.invoke(SB_TO_STRING),
MethodReturn.of(TypeDescription.STRING)
));
}
}
像这样应用它
builder
.method(ElementMatchers.named("toString"))
.intercept(new ToStringImplementation());