用动态生成的类替换反射调用

问题描述 投票:1回答:2

我有一个与此类似的界面:

public interface Getter {
    Object get(Params params);
}

我使用对另一种方法的反射调用实现:

public class GetterImpl implements Getter {

    private final Object target;
    private final Method method; //doStuff method

    public GetterImpl(Object target, Method method) {
        this.target = target;
        this.method = method;
    }

    @Override
    public Object get(Params params) {
        //both the target and arguments depend on Params
        return method.invoke(chooseTarget(params), prepareArgs(params));
    }

    private Object chooseTarget(Params params) {
        if (params.getTargetOverride() != null) {
            return params.getTargetOverride();
        }
        return target;
    }

    private Object[] prepareArgs(Params params) {
        ...
    }
}

是否可以生成具有等效逻辑但没有反射的实现Getter的类?实际上是这样的类:

public class GeneratedGetterImpl implements Getter {

    ...

    @Override
    public Object get(Params params) {
        //somehow call doStuff directly (different method for each generated impl)
        return target.doStuff(prepareArgs(params));
    }
}

我正在研究使用Byte Buddy即时生成这样的类,但是所有示例都提供了某种静态已知的方法拦截器,并且从不委托给动态选择的目标和方法。

这显然不是一件容易的事,但是可以用Byte Buddy完成吗?还是其他图书馆?

UPDATE:

到目前为止,这是我最好的尝试(它确实有效):

Target target = new Target();
Method method = Target.class.getMethod("doStuff", Book.class);

//Helper class that calculates the new arguments based on the original
Prepare prepare = new Prepare();
Method doPrep = Prepare.class.getMethod("doPrep", Params.class);

Getter getter = (Getter) new ByteBuddy()
            .subclass(Object.class)
            .implement(Getter.class)
            .method(named("get")).intercept(
                    MethodCall.invoke(method).on(target)
                            .withMethodCall(
                                    MethodCall.invoke(doPrep).on(prepare).withAllArguments()
                            ))
            .make()
            .load(getClass().getClassLoader())
            .getLoaded()
            .newInstance();

public static class Prepare {

    public Book doPrep(Params params) {
        return new Book(params.getTitle());
    }
}

这满足了我的要求,我现在正试图找出如何最好地摆脱助手类的方法。

java code-generation bytecode java-bytecode-asm byte-buddy
2个回答
3
投票

如果我们限制将接口绑定到匹配的目标方法,则JRE中确实已经存在这样的设施。

public static void main(String[] args) throws NoSuchMethodException {
    Function<Double,Double> f1 = create(Math.class.getMethod("abs", double.class));
    System.out.println(f1.apply(-42.0));

    Map<Double,Double> m = new HashMap<>();
    Function<Double,Double> f2 = create(Map.class.getMethod("get", Object.class), m);
    m.put(1.0, 123.0);
    System.out.println(f2.apply(1.0));
}

static Function<Double,Double> create(Method m) {
    MethodHandles.Lookup l = MethodHandles.lookup();
    MethodType t = MethodType.methodType(Double.class, Double.class);
    try {
        return (Function)LambdaMetafactory.metafactory(l, "apply",
                MethodType.methodType(Function.class), t.erase(), l.unreflect(m), t)
                .getTarget().invoke();
    } catch(Throwable ex) {
        throw new IllegalStateException(ex);
    }
}
static Function<Double,Double> create(Method m, Object target) {
    MethodHandles.Lookup l = MethodHandles.lookup();
    MethodType t = MethodType.methodType(Double.class, Double.class);
    try {
        return (Function)LambdaMetafactory.metafactory(l, "apply",
                MethodType.methodType(Function.class, m.getDeclaringClass()),
                t.erase(), l.unreflect(m), t)
                .getTarget().invoke(target);
    } catch(Throwable ex) {
        throw new IllegalStateException(ex);
    }
}
42.0
123.0

这表明包括了通用功能所需的诸如自动装箱和转换之类的修改,但是参数或结果的任何其他修改都是不可能的,必须通过预先存在的装饰代码来执行。最值得注意的是,不包括varargs处理。

The documentation详尽无遗。强烈建议您在使用课程之前,先阅读所有详细信息。但是,您在这里可能做错的事情类似于在实现自己的字节码生成器时可能做错的事情。


1
投票

[使用字节伙伴,您可以创建一个代表代理方法的MethodCall实例,并将其用作实现。我假设您调查了需要更静态模型的委托:

MethodCall.invoke(SomeClass.class.getMethod("foo")).with(...)

您还可以提供其他方法调用实例作为方法的参数,以实现示例代码中的功能。

© www.soinside.com 2019 - 2024. All rights reserved.