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

643 Views Asked by At

I am trying to use LambdaMetafactory to replace reflection,but I have a problem.If I use a specific class then it works well,just like this:

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

But if I use Class<?> to replace the specific class ,then it won't work,just like this:

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

Here is the exception:

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)

The Line 182 is:

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

I know just use static method can solve this problem,but I want to know is there any other way to solve it without changing the non-static to static.

Here is the 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;
        }
    }

In this method,I use reflection to find the static method getInstance in Class,and return a instance,it is just a simple singleton.

1

There are 1 best solutions below

2
On BEST ANSWER

The problem is that you are using

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

resp.

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

Calling bindTo creates a MethodHandle, whose first parameter is bound to the specified object instance, however, you are ignoring the new MethodHandle. Therefore, you need to specify the instance again as an argument when invoking the unbound handle.

For this invocation, the compile-time type matters. In the first example, the compile-time type of the argument is correct, hence the invocation has the right signature (TestService)Function.

In the second example, the compile-time type of instance is Object, hence the signature compiled into the byte code will be (Object)Function, which is not an exact match. Using cls.cast(…) does not help, as that will perform a runtime check and assert that generic types match, if you used a type variable here, but both is irrelevant to the byte code of the invokeExact call.

You have two options. You can simply use invoke instead, which allows type conversions during the invocation (sacrificing a bit performance)

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

or you change the code to do what seems to be originally intended, bind the first argument before the invocation:

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

since for the pre-bound method handle, no argument is needed, you have an exact call again (bindTo is not signature polymorphic, hence, will check the type of the argument only at runtime).

You could also write this as a one-liner

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