MethodHandle 查找问题

问题描述 投票:0回答:1

我最近需要制作一个相对简单的事件系统。 (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
类,但我不确定这是否是最好的主意。

谢谢大家的帮助!

java events lambda reflection
1个回答
0
投票

如果您的问题是遇到类似

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;
}
© www.soinside.com 2019 - 2024. All rights reserved.