生成调用另一个类的静态方法并使用多个字段作为参数的代码。

huangapple go评论70阅读模式
英文:

Generate code that calls static method from another class and uses several fields as arguments

问题

我已经挣扎了一段时间,试图找到解决这个问题的方法。希望你能帮我解决。

我试图生成一个方法,该方法调用另一个类中的静态方法,使用一些已定义的字段:

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) {
        // 一些操作
        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(
                // 第一个参数
                MethodVariableAccess.loadThis(),
                this.getField(thisType, "someField"),

                // 第二个参数
                MethodVariableAccess.loadThis(),
                this.getField(thisType, "otherField"),

                // 调用 of 方法并返回结果
                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 来解决问题不是一个选项。

英文:

I've been struggling for a while trying to find a solution to this problem. Hope you can help me out.

I'm trying to generate a method that calls a static method from another class using some already defined fields:

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

Expected result:

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;
}
}

I've tried several things, closest one:

public class ModelProcessor implements Plugin {
@Override
public Builder&lt;?&gt; apply(final Builder&lt;?&gt; builder,
final TypeDescription typeDescription,
final ClassFileLocator classFileLocator) {
return builder.defineMethod(&quot;getCacheKey&quot;, 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(&quot;of&quot;, 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, &quot;someField&quot;),
// second param
MethodVariableAccess.loadThis(),
this.getField(thisType, &quot;otherField&quot;),
// 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();
}
}

However, generated code is as follows (decompiled with Intellij Idea):

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

Changing the signature of SimpleCacheKey.of and trying to workaround the problem with a List is not an option.

答案1

得分: 2

你正在调用一个可变参数方法,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 有一个专门的构建器用于此目的,但至少这是一种实现方法。

我认为:通常编写您想要生成的字节码的 Java 版本是一个不错的方法。这样,您可以比较 javac 生成的字节码和 Byte Buddy 生成的字节码。

英文:

You are calling a vararg method, java bytecode doesnt have that. So you need to create an actual array of the correct type to call the method.

@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, &quot;field1&quot;)),
new StackManipulation.Compound(MethodVariableAccess.loadThis(),
this.getField(thisType, &quot;field2&quot;)))
), MethodInvocation.invoke(SIMPLE_CACHE_KEY_OF) //
, MethodReturn.of(TypeDescription.STRING)));
}

Maybe byte-buddy has a special builder for that, but at least thats one way of doing that.

Imo: it is often a good approach to write a java version of the bytecode you want to generate. That way you can compare the javac bytecode and bytebuddy bytecode.

huangapple
  • 本文由 发表于 2020年3月16日 01:38:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/60695798.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定