我正在尝试使用Byte Buddy将JSON模式编译为JavaBeans。我有班级,领域和getter / setter生成工作。我也想生成toString / equals / hashCode,但似乎这样做需要为我正在定义的类中的字段获取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));
}
答案 0 :(得分:0)
我想出了一个基于this answer的解决方案。这是一个简化版本。
此示例将采用一个包含名为<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="first" style="width: 200px; height: 300px; border: 3px solid black">
</div>
<div id="second" style="width: 200px; height: 300px; border: 3px solid black; display: none">
</div>
的单个字符串字段的类,并生成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)
));
}
}