【问题标题】:How do I refer to a field in the class which I am currently defining with Byte Buddy?如何引用我当前使用 Byte Buddy 定义的类中的字段?
【发布时间】:2017-02-28 18:29:44
【问题描述】:

我正在尝试使用 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));
}

【问题讨论】:

    标签: java byte-buddy


    【解决方案1】:

    我想出了一个基于this answer 的解决方案。这是一个简化版本。

    此示例将采用一个包含一个名为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 添加一个默认实现。
    • @RafaelWinterhalter:感谢您的反馈。还有一个问题:有没有一种方法可以解决字段类型的StringBuilder::append 的最佳重载,而不是手动指定它?我一直在看MethodDelegation,但我看不到在这种情况下使用它的方法。
    • 在这种情况下你应该把字节码编码出来,关于原语,这样效率更高。
    • 是的,我真的不想要一个委托,我只是想找到一些方法来找到最具体的方法(根据JLS 15.12.2),使用TypeDescriptions 作为参数类型,所以我可以为我的调用发出它。
    猜你喜欢
    • 1970-01-01
    • 2014-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多