我最近需要制作一个相对简单的事件系统。 (Id -> Consumer)这将能够订阅基于注释的
Method
。然后我想起 Java 不是 C#,并尝试寻找一种方法来实现这一点。在许多类似的线程之后,我发现了这个 GitHub Gist,并尝试实现它。我成功地做到了,但是没有尽早意识到 MethodHandles.lookup()
方法是特定于位置的,并且仅适用于调用它的类。
消费者工厂
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());
}
}
我可以做些什么来保持这种想法功能吗?理想情况下最终会是类似
Map<String, Consumer<Event>>
的样子。我正在考虑强制创建一个 Lookup
类,但我不确定这是否是最好的主意。
谢谢大家的帮助!
如果您的问题是遇到类似
LambdaConversionException: Invalid caller: …
的异常,则问题出在这一行:
MethodHandles.Lookup lookup = MethodHandles.lookup().in(implType);
你只需将其更改为
MethodHandles.Lookup lookup = MethodHandles.lookup();
这会在
ConsumerFactory
的上下文中执行操作,并要求允许 ConsumerFactory
访问目标方法。但是,如果您当前的代码成功地在查找对象上执行 findVirtual
并继续以上述异常结束,则意味着已给出可访问性。
但请注意,代码不必要地复杂。尽管对
getAllMethods
的 getMethods()
调用确实已经返回所有 Class
方法,包括从超级接口继承的方法,但 public
仍会迭代超类层次结构,而 getSuperclass()
无论如何都会返回接口的 null
。
此外,如何时在 Java 中使用 LinkedList 而不是 ArrayList 中所述? 很少需要
LinkedList
,此用例也不例外。
那么,
isAbstract
排除了isDefault
,所以这个测试是多余的。另一方面,必须跳过与 public
的 java.lang.Object
方法匹配的方法。如果不这样做,就会失败,例如Comparator
。问题是您是否想要修复 findLambdaMethod
以覆盖所有情况,还是完全放弃它,因为您只对 Consumer
感兴趣,我们知道该方法是 void accept(Object)
。
此外,将
Method
转换为 MethodHandle
不需要手动完成。考虑到所有的简化,我们最终得到
public class ConsumerFactory {
private final Map<Method, Consumer<?>> methodCache = new HashMap<>();
public ConsumerFactory() {}
public <T, L> Consumer<T> createConsumer(L instance, Method implMethod) throws Throwable {
Consumer<T> cached = (Consumer<T>) methodCache.get(implMethod);
if(cached==null) {
// avoid cryptic errors by checking this explicitly
if(implMethod.getParameterCount() != 1 || Modifier.isStatic(implMethod.getModifiers()))
throw new IllegalArgumentException("not suitable for Consumer: " + implMethod);
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle implMethodHandle = lookup.unreflect(implMethod);
MethodType implMethodType = MethodType.methodType(void.class, implMethodHandle.type().parameterType(1));
MethodType invokedMethodType = MethodType.methodType(Consumer.class, implMethod.getDeclaringClass());
CallSite metaFactory = LambdaMetafactory.metafactory(
lookup,
"accept", invokedMethodType, MethodType.methodType(void.class, Object.class),
implMethodHandle, implMethodType);
Consumer<T> consumer = (Consumer<T>) metaFactory.getTarget().invoke(instance);
methodCache.put(implMethod, consumer);
return consumer;
}
return cached;
}
}
但请记住,您也可以创建一个
Consumer
,在 invoke
上调用 Method
,甚至可以通过 lambda 表达式。所以上面的代码并不是严格需要的。如果 invoke
方法失败,在 Method
上调用 LambdaMetafactory
也可以作为后备方案。
如果您想保留查找要实现函数式接口的方法的方法,这里有一个修订版本:
private Method findLambdaMethod(Class<?> type) {
if(!type.isInterface()) {
throw new IllegalArgumentException("This must be interface: " + type);
}
Method targetMethod = null;
for(Method method: type.getMethods()) {
if(isCandidate(method)) {
if(targetMethod != null) {
throw new IllegalArgumentException("This isn't functional interface: " + type.getName());
}
targetMethod = method;
}
}
if(targetMethod == null) {
throw new IllegalArgumentException("This isn't functional interface: " + type.getName());
}
return targetMethod;
}
private boolean isCandidate(Method method) {
if(!Modifier.isAbstract(method.getModifiers()))
return false;
// check for redeclarations of public java.lang.Object methods
if(method.getParameterCount() == 0) {
String name = method.getName();
return !name.equals("hashCode") && !name.equals("toString");
}
if(method.getParameterCount() == 1)
return !method.getName().equals("equals") || method.getParameterTypes()[0] != Object.class;
return true;
}