如何在运行时使用 LambdaMetafactory 在动态 class 中访问非静态方法

How to access a non-static method in dynamic class with LambdaMetafactory during the runtime

我正在尝试使用 LambdaMetafactory 来替换反射,但我有一个 problem.If 我使用特定的 class 然后它运行良好,就像这样:

        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodType type = MethodType.methodType(ResponseMsg.class,Map.class);

        MethodHandle mh = lookup.findVirtual(TestService.class,"testMethod",type);

        TestService ins = TestService.getInstance();

        MethodHandle factory = LambdaMetafactory.metafactory(
                lookup, "apply", MethodType.methodType(Function.class,TestService.class),
                type.generic(), mh, type).getTarget();

        factory.bindTo(ins);

        Function lambda = (Function) factory.invokeExact(ins);

但是如果我用Class<?>来代替具体的class,那就不行了,就像这样:

    public static Function generateLambda(@NotNull Class<?> cls,@NotNull String method) {
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodType type = MethodType.methodType(RETURN_TYPE,PARM_TYPE);

    try {
        MethodHandle mh = lookup.findVirtual(cls,method,type);
        Object instance = getInstance(cls);
        if(instance == null) {
            return null;
        }
        MethodHandle factory = LambdaMetafactory.metafactory(
                lookup, "apply", MethodType.methodType(Function.class,cls),
                type.generic(), mh, type).getTarget();

        factory.bindTo(cls.cast(instance));

        return (Function) factory.invokeExact(cls.cast(instance));
    } catch (Throwable e) {
        logger.error("get Function fail, cause :" ,e);
        return null;
    }
}

例外情况:

java.lang.invoke.WrongMethodTypeException: expected (TestService)Function but found (Object)Function
    at java.lang.invoke.Invokers.newWrongMethodTypeException(Invokers.java:298)
    at java.lang.invoke.Invokers.checkExactType(Invokers.java:309)
    at com.utils.cache.ClassUtils.generateLambda(ClassUtils.java:182)

第 182 行是:

return (Function) factory.invokeExact(cls.cast(instance));

我知道只用静态方法就可以解决这个问题,但我想知道有没有其他方法可以在不将非静态更改为静态的情况下解决它。

这里是 getInstance:

 private static Object getInstance(@NotNull Class<?> cls) {
        try {
            Method getInstanceMethod = cls.getDeclaredMethod("getInstance");
            return getInstanceMethod.invoke(null);
        } catch (Exception e) {
            logger.error("get instance fail, cause :" ,e);
            return null;
        }
    }

在这个方法中,我使用反射找到了Class中的静态方法getInstance,return一个实例,它只是一个简单的单例。

问题是你正在使用

factory.bindTo(ins);
Function lambda = (Function) factory.invokeExact(ins);

相应

factory.bindTo(cls.cast(instance));
return (Function) factory.invokeExact(cls.cast(instance));

调用bindTo创建一个MethodHandle,其第一个参数绑定到指定的对象实例,但是,您忽略了新的MethodHandle。因此调用未绑定句柄时需要再次指定实例作为参数。

对于此调用,编译时类型很重要。在第一个示例中,参数的编译时类型是正确的,因此调用具有正确的签名 (TestService)Function.

在第二个例子中,instance的编译时类型是Object,因此编译成字节码的签名将是(Object)Function,这不是完全匹配.使用 cls.cast(…) 没有帮助,因为这将执行 运行时检查 并断言泛型类型匹配,如果您在此处使用类型变量,但两者都与字节码无关invokeExact 电话。

你有两个选择。您可以简单地使用 invoke 代替,它允许在调用期间进行类型转换(牺牲一点性能)

// unused factory.bindTo call omitted
return (Function) factory.invoke(instance); // noneffective cls.cast omitted

或者您更改代码以执行看似最初预期的操作,在调用之前绑定第一个参数:

factory = factory.bindTo(instance);
return (Function)factory.invokeExact();

因为预绑定方法句柄不需要参数,你又一次精确调用(bindTo不是签名多态,因此只会在运行时检查参数的类型)。

你也可以把它写成一行

return (Function)factory.bindTo(instance).invokeExact();