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

kaq*_*qao 2 java code-generation bytecode java-bytecode-asm byte-buddy

我有一个类似的界面:

public interface Getter {
    Object get(Params params);
}
Run Code Online (Sandbox Code Playgroud)

我使用对不同方法的反射调用来实现:

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) {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

是否可以生成一个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));
    }
}
Run Code Online (Sandbox Code Playgroud)

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

这显然不是一项微不足道的任务,但这可以通过 Byte Buddy 完成吗?还是不同的图书馆?

更新:

这是我迄今为止最好的尝试:

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

//Helper class that computes 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());
    }
}
Run Code Online (Sandbox Code Playgroud)

这符合我的要求,但前提是目标方法采用 1 个参数(Book在我的情况下)。我正在努力弄清楚如何让它返回一个数组,然后在调用目标方法时传播该数组。

例如

public static class Prepare {
    //returns all the arguments
    public Object[] doPrep(Params params) {
        return new Object[] { new Book(params.getTitle()) };
    }
}
Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 6

如果我们将其限制为将接口绑定到匹配的目标方法,则 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);
    }
}
Run Code Online (Sandbox Code Playgroud)
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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这表明包括通用函数所需的自动装箱和强制转换等适应,但参数或结果的任何其他适应都是不可能的,必须由预先存在的装饰代码执行。最值得注意的是,不包括可变参数处理。

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