I've recently needed to make a relatively simple events system. (Id -> Consumer) This would be able to subscribe a Method
based off annotation. Then I remembered that Java isn't C# and attempted to look for a way to accomplish this. After many similar threads, I came across this GitHub Gist, and attempted to implement it. I did so successfully, however did not realize early enough that the MethodHandles.lookup()
method is location specific and only applies to the class that calls it.
ConsumerFactory
public class ConsumerFactory {
private final Method consumerMethod;
private final MethodType consumerMethodType;
private final Map<Method, Consumer<?>> methodCache = new HashMap<>();
public ConsumerFactory() {
consumerMethod = findLambdaMethod(Consumer.class);
consumerMethodType = MethodType.methodType(consumerMethod.getReturnType(), consumerMethod.getParameterTypes());
}
public <T, L> Consumer<T> createConsumer(L instance, Method implMethod) throws Throwable {
Consumer<T> cached = (Consumer<T>) methodCache.get(implMethod);
if(cached==null) {
Class<?> implType = implMethod.getDeclaringClass();
MethodHandles.Lookup lookup = MethodHandles.lookup().in(implType);
MethodType implMethodType = MethodType.methodType(implMethod.getReturnType(), implMethod.getParameterTypes());
MethodHandle implMethodHandle = lookup.findVirtual(implType, implMethod.getName(), implMethodType);
MethodType invokedMethodType = MethodType.methodType(Consumer.class, implType);
CallSite metaFactory = LambdaMetafactory.metafactory(
lookup,
consumerMethod.getName(), invokedMethodType, consumerMethodType,
implMethodHandle, implMethodType);
MethodHandle factory = metaFactory.getTarget();
Consumer<T> consumer = (Consumer<T>) factory.invoke(instance);
methodCache.put(implMethod, consumer);
return consumer;
}
return cached;
}
private Method findLambdaMethod(Class<?> type) {
if (!type.isInterface()) {
throw new IllegalArgumentException("This must be interface: " + type);
}
Method[] methods = getAllMethods(type);
if (methods.length == 0) {
throw new IllegalArgumentException("No methods in: " + type.getName());
}
Method targetMethod = null;
for (Method method : methods) {
if (isInterfaceMethod(method)) {
if (targetMethod != null) {
throw new IllegalArgumentException("This isn't functional interface: " + type.getName());
}
targetMethod = method;
}
}
if (targetMethod == null) {
throw new IllegalArgumentException("No method in: " + type.getName());
}
return targetMethod;
}
private Method[] getAllMethods(Class<?> type) {
LinkedList<Method> result = new LinkedList<>();
Class<?> current = type;
do {
result.addAll(Arrays.asList(current.getMethods()));
} while ((current = current.getSuperclass()) != null);
return result.toArray(new Method[0]);
}
private boolean isInterfaceMethod(Method method) {
return !method.isDefault() && Modifier.isAbstract(method.getModifiers());
}
}
Is their anything I could do to still have this sort of idea function? Ideally end up as something like Map<String, Consumer<Event>>
. I was considering forcefully creating a Lookup
class, but I'm not sure if that would be the best idea.
Thank you all for the help!
Aucun commentaire:
Enregistrer un commentaire