Создайте код, который вызывает метод stati c из другого класса и использует несколько полей в качестве аргументов - PullRequest
1 голос
/ 15 марта 2020

Я некоторое время пытался найти решение этой проблемы. Надеюсь, вы мне поможете.

Я пытаюсь сгенерировать метод, который вызывает метод stati c из другого класса, используя некоторые уже определенные поля:

class Test {
   private String someField;
   private String otherField;
}

Ожидаемый результат:

class Test {
   private String someField;
   private String otherField;

   public String getCacheKey() {
      return SimpleCacheKey.of(this.someField, this.otherField);
   }
}

class SimpleCacheKey {
    public static String of(final Object... values) {
        // Some Operations
        return computed_string;
    }
}

Я пробовал несколько самых близких вещей:

public class ModelProcessor implements Plugin {
    @Override
    public Builder<?> apply(final Builder<?> builder,
                            final TypeDescription typeDescription,
                            final ClassFileLocator classFileLocator) {

        return builder.defineMethod("getCacheKey", String.class, Visibility.PUBLIC)
                .intercept(new SimpleCacheKeyImplementation());
    }

    @Override
    public void close() throws IOException {

    }

    @Override
    public boolean matches(final TypeDescription typeDefinitions) {
        return true;
    }
}

public class SimpleCacheKeyImplementation implements Implementation {
    private static final MethodDescription SIMPLE_CACHE_KEY_OF = getOf();

    @SneakyThrows
    private static MethodDescription.ForLoadedMethod getOf() {
        return new MethodDescription.ForLoadedMethod(SimpleCacheKey.class.getDeclaredMethod("of", Object[].class));
    }

    @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(
                // first param
                MethodVariableAccess.loadThis(),
                this.getField(thisType, "someField"),

                // second param
                MethodVariableAccess.loadThis(),
                this.getField(thisType, "otherField"),

                // call of and return the result
                MethodInvocation.invoke(SIMPLE_CACHE_KEY_OF),
                MethodReturn.of(TypeDescription.STRING)
        ));
    }

    private StackManipulation getField(final TypeDescription thisType, final String name) {
        return FieldAccess.forField(thisType.getDeclaredFields()
                .filter(ElementMatchers.named(name))
                .getOnly()
        ).read();
    }
}

Однако сгенерированный код выглядит следующим образом (декомпилирован с Intellij Idea):

public String getCacheKey() {
        String var10000 = this.name;
        return SimpleCacheKey.of(this.someValue);
    }

Изменение подписи SimpleCacheKey.of и попытка обойти проблему с List не вариант.

1 Ответ

1 голос
/ 16 марта 2020

Вы вызываете метод vararg, java байт-код не имеет этого. Таким образом, вам нужно создать реальный массив правильного типа для вызова метода.

@Override
public ByteCodeAppender appender(final Target implementationTarget) {
    final TypeDescription thisType = implementationTarget.getInstrumentedType();

    return new ByteCodeAppender.Simple(Arrays.asList(ArrayFactory.forType(TypeDescription.Generic.OBJECT)
            .withValues(Arrays.asList( //
                    new StackManipulation.Compound(MethodVariableAccess.loadThis(),
                            this.getField(thisType, "field1")),
                    new StackManipulation.Compound(MethodVariableAccess.loadThis(),
                            this.getField(thisType, "field2")))
            ), MethodInvocation.invoke(SIMPLE_CACHE_KEY_OF) //
            , MethodReturn.of(TypeDescription.STRING)));

}

Возможно, у byte-buddy есть специальный конструктор для этого, но по крайней мере это один из способов сделать это.

Imo: часто хорошим подходом является написание java версии байт-кода, который вы хотите сгенерировать. Таким образом, вы можете сравнить байт-код javac и байт-код.

...