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
?
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 MapHandler
s, 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 MapHandler
s 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.