Is it possible to get a method reference or function object from an object by reflection?

user9613668 :

There is A class.

class A {
    Mono<Response> testMap(Mono<Request> reqMono)
}

There is the functional interface

interface MapHandler {
    Mono<?> handle(Mono<?> reqMono)
}

Now I can write this

{
A a = new A();
MapHandler handler = a::testMap;
}

And I want to build a tool that could detect all the MapHandler in a bean (object) and collect them.

I've tried this.

List<MapHandler> list = initedList;
Method method = bean.getClass().getDeclaredMethods()[0];
list.put("methodName", req -> {
    return (Mono<?>) method.invoke(bean, req);
})

Is it possible to do it by MethodHandle or LambdaMetaFactory?

HTNW :

A rough sketch of a solution that explicitly uses LambdaMetafactory in the way you seem to want is:

// the signature of the method returned by LambdaMetaFactory
// it takes an object of bean's type and makes a MapHandler that calls one
// of its instance methods
MethodType mkLambdaType = MethodType.methodType(MapHandler.class, bean.getClass());
// the signature of the method in MapHandler being implemented
MethodType handleType = MethodType.methodType(Mono.class, Mono.class);
MethodHandles.Lookup lookup = MethodHandles.lookup();

// FYI this won't search in supertypes
// getMethods() will, but you only get public ones even if you have more privileged access
// you decide what to do here; the loop body is the important thing
for(Method method : bean.getClass().getDeclaredMethods()) {
    if(Modifier.isStatic(method.getModifiers())) continue;
    try {
        MethodHandle target = lookup.unreflect(method);
        CallSite mkLambda = LambdaMetafactory.metafactory
            (lookup, "handle", mkLambdaType, handleType, target, handleType);
        list.add((MapHandler)mkLambda.getTarget().invoke(bean));
    } catch(IllegalAccessException | LambdaConversionException e) {
        // because I am lazy, I'm not checking that method has the correct type
        // I'm letting LambdaMetafactory throw if that's not the case
        // if you choose not to use LambdaMetafactory, you may have to implement
        // this type-checking
        continue;
    } catch(Throwable t) {
        // Throwables come from the MethodHandle#invoke call
        // but nothing should be thrown at all, because LambdaMetafactory
        // produces its errors from metafactory, early, which are caught above
        throw new RuntimeException("Unexpected error during reflection", t);
    }
}

I believe this is very wasteful. A common implementation of LambdaMetafactory will likely create a whole new class in the metafactory call, returning a CallSite pointing to a constructor or similar. This means that each MapHandler you get is its own anonymous class, created at runtime. In contrast, your original idea of using a lambda to invoke a Method is much nicer to the JVM. The lambda causes the creation of a single LambdaMetafactory class, which holds method and bean as instance variables. After the first run through the code, where the invokedynamic is bootstrapped, each MapHandler is created simply by instantiating this anonymous class. My LambdaMetafactory solution only seems acceptable if you only need relatively few MapHandlers, but each is called so often that the overhead of Method#invoke is too high.


So I've gone ahead and done some quick benchmarking. In your use case, where you initialize MapHandlers at program start and then just invoke them, my technique and yours are about the same. They are both so fast that I cannot actually measure the difference in time taken to invoke the different kinds of MapHandler. In general, LambdaMetafactory is worse, because creating the anonymous class takes a lot of time. In fact, the more classes you make, the longer it takes. Meanwhile, the method.invoke lambda simply has to construct one object, which is often thousands of times faster.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=105027&siteId=1