2

我正在尝试使用 LambdaMetafactory 来替换反射,但我有一个问题。如果我使用特定的类,那么它工作得很好,就像这样:

        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<?>用来替换特定的类,那么它就不起作用了,就像这样:

    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,并返回一个实例,它只是一个简单的单例。

4

1 回答 1

1

问题是您正在使用

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

分别

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

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

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

在第二个示例中,编译时类型instanceObject,因此编译为字节码的签名将为(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();
于 2018-11-05T17:19:30.567 回答