How should I use LambdaMetaFactory in my use case?

Despite the fact that I have read all the documentation that I know about, I cannot solve the problem of using lambdas to execute the method. To give a little background, my use case is a plugin system. I use annotation (@EventHandle) which can be assigned to any method. I use reflection and repeat each method in the class and check if it has an annotation if it is added to the handler object (which is added to the list to process each “checkmark”). Here is my handler class:

package me.b3nw.dev.Events; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import java.lang.invoke.*; import java.lang.reflect.Method; import java.lang.reflect.Type; @Slf4j public class Handler { @Getter private final Method method; @Getter private final EventHandle handle; private final MethodHandles.Lookup lookup; private final MethodHandle methodHandle; private final EventHandler invoker; public Handler(Method method, EventHandle handle) throws Throwable { this.method = method; log.info(method.getGenericReturnType() + ""); for(Type type : method.getParameterTypes()) { log.info(type.getTypeName()); } this.handle = handle; this.lookup = MethodHandles.lookup(); this.methodHandle = lookup.unreflect(method); log.info("" + methodHandle.type().toMethodDescriptorString()); this.invoker = (EventHandler) LambdaMetafactory.metafactory(lookup, "handle", MethodType.methodType(EventHandler.class), methodHandle.type(), methodHandle, methodHandle.type()).getTarget().invokeExact(); } public void invoke(GameEvent evt) throws Throwable { invoker.handle(evt); } } 

In the current iteration of this class, I throw directly into the EventHandler functional interface, source:

 package me.b3nw.dev.Events; @FunctionalInterface public interface EventHandler { boolean handle(GameEvent evt); } 

I am currently getting the following error:

 ERROR mbdHGamemodeHandler - java.lang.AbstractMethodError: me.b3nw.dev.Events.Handler$$Lambda$3/1704984363.handle(Lme/b3nw/dev/Events/GameEvent;)Z at me.b3nw.dev.Events.Handler.invoke(Handler.java:40) ~[classes/:na] at me.b3nw.dev.Handlers.GamemodeHandler.userEventTriggered(GamemodeHandler.java:34) ~[classes/:na] 

GamemodeHandler simply calls the invoke method in the Handler class.

Thus, it throws AbstractMethodError when I just pass the EventHandler event and execute when I don't use it, I get another error that:

 java.lang.invoke.WrongMethodTypeException: expected ()EventHandler but found ()void at java.lang.invoke.Invokers.newWrongMethodTypeException(Invokers.java:294) ~[na:1.8.0_45] at java.lang.invoke.Invokers.checkExactType(Invokers.java:305) ~[na:1.8.0_45] at me.b3nw.dev.Events.Handler.invoke(Handler.java:40) ~[classes/:na] at me.b3nw.dev.Handlers.GamemodeHandler.userEventTriggered(GamemodeHandler.java:34) ~[classes/:na] 

Modified Handler to reflect changes:

 package me.b3nw.dev.Events; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import java.lang.invoke.*; import java.lang.reflect.Method; import java.lang.reflect.Type; @Slf4j public class Handler { @Getter private final Method method; @Getter private final EventHandle handle; private final MethodHandles.Lookup lookup; private final MethodHandle methodHandle; private final MethodHandle invoker; public Handler(Method method, EventHandle handle) throws Throwable { this.method = method; log.info(method.getGenericReturnType() + ""); for(Type type : method.getParameterTypes()) { log.info(type.getTypeName()); } this.handle = handle; this.lookup = MethodHandles.lookup(); this.methodHandle = lookup.unreflect(method); log.info("" + methodHandle.type().toMethodDescriptorString()); this.invoker = LambdaMetafactory.metafactory(lookup, "handle", MethodType.methodType(EventHandler.class), methodHandle.type(), methodHandle, methodHandle.type()).getTarget(); } public void invoke(GameEvent evt) throws Throwable { invoker.invokeExact(); } } 

This class has a method that is annotated and should implement the signature of the functional interface, but .. obviously not :( Here is the class:

 package me.b3nw.dev.Gamemode; import lombok.extern.slf4j.Slf4j; import me.b3nw.dev.Events.EventHandle; import me.b3nw.dev.Events.GameEvent; @Slf4j public class Vanilla extends Gamemode { public void testMethod() { } @EventHandle(type = EventHandle.Type.NICKANNOUNCE) public boolean testMethod2(GameEvent evt) { log.info("Fuck yeah!"/* + evt*/); return true; } } 

How can I fix this, I'm using lambdas completely wrong here?

Thanks.

+5
source share
1 answer

If you looked at the output of the log, you noticed that your signature of the target method looks like (Lme/b3nw/dev/Events/Vanilla;Lme/b3nw/dev/Events/GameEvent;)Z , in other words, since your target method is a method instance, he needs an instance of his class (i.e. Vanilla ) as the first argument.

If you do not provide an instance during lambda creation, but pass the signature of the target methods as a functional signature, the created lambda instance will have a method such as

 boolean handle(Vanilla instance, GameEvent evt) { instance.testMethod2(evt); } 

which does not match the actual interface method

 boolean handle(GameEvent evt); 

which you are trying to call. Therefore you get AbstractMethodError . LambdaMetaFactory intended for the compiler of generated code in the first place and does not perform expensive checks, i.e. Does not attempt to define a functional interface method to compare it with the provided signature.

So, you need to provide an instance on which the target method should be called:

 public Handler(Method method, Object instance, EventHandle handle) throws Throwable { this.method = method; log.info(method.getGenericReturnType() + ""); for(Type type : method.getParameterTypes()) { log.info(type.getTypeName()); } this.handle = handle; this.lookup = MethodHandles.lookup(); this.methodHandle = lookup.unreflect(method); MethodType type = methodHandle.type(); // add the type of the instance to the factory method MethodType factoryType=MethodType.methodType(EventHandler.class,type.parameterType(0)); // and remove it from the function signature type=type.dropParameterTypes(0, 1); log.info("" + type.toMethodDescriptorString()); this.invoker = (EventHandler)LambdaMetafactory.metafactory(lookup, "handle", factoryType, type, methodHandle, type).getTarget() // use invoke instead of invokeExact as instance is declared as Object .invoke(instance); } 

Of course, you also need to adapt code that collects annotated methods to go through the instance you're working on.

Please note that all this applies to your first version. I could not get the meaning of your "modified handler".

+3
source

All Articles