MethodHandle 查找问题

MethodHandle Lookup Issues

提问人:SwordOfSouls 提问时间:10/28/2023 更新时间:10/31/2023 访问量:63

问:

我最近需要制作一个相对简单的事件系统。(id -> 使用者)这将能够订阅基于注释的注释。然后我想起 Java 不是 C#,并试图寻找一种方法来实现这一点。在许多类似的线程之后,我遇到了这个 GitHub Gist,并尝试实现它。我成功地做到了这一点,但是没有足够早地意识到该方法是特定于位置的,并且仅适用于调用它的类。MethodMethodHandles.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 事件 lambda 反射

评论


答:

2赞 Holger 10/31/2023 #1

如果你的问题是你得到一个异常,如,问题出在以下行:LambdaConversionException: Invalid caller: …

MethodHandles.Lookup lookup = MethodHandles.lookup().in(implType);

你只需要把它改成

MethodHandles.Lookup lookup = MethodHandles.lookup();

这将在 的上下文中执行操作,并要求允许访问目标方法。但是,如果您当前的代码成功地在查找对象上执行了操作,并继续以上述异常结束,则意味着已提供可访问性。ConsumerFactoryConsumerFactoryfindVirtual

但请注意,代码过于复杂。尽管调用了 a ,但迭代超类层次结构已经返回所有方法,包括从超级接口继承的方法,但无论如何都会返回接口。getAllMethodsgetMethods()ClasspublicgetSuperclass()null

此外,正如何时在 Java 中使用 LinkedList 而不是 ArrayList 中所解释的,很少需要 a,这个用例也不例外。LinkedList

然后,排除 ,所以这个测试是多余的。另一方面,必须跳过与方法匹配的方法。如果不这样做,它就会失败,例如。问题是您是要修复以涵盖所有情况还是完全放弃它,因为您只对我们知道该方法是 .isAbstractisDefaultpublicjava.lang.ObjectComparatorfindLambdaMethodConsumervoid accept(Object)

此外,从 a 到 a 的转换不需要手动完成。考虑到所有简化,我们最终得到MethodMethodHandle

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;
    }
}

但请记住,您也可以在 上创建调用,甚至可以通过 lambda 表达式。所以上面的代码并不是严格需要的。如果方法失败,调用 a 也可以作为回退。ConsumerinvokeMethodinvokeMethodLambdaMetafactory

如果要保留用于查找要为函数接口实现的方法的方法,下面是修订后的版本:

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;
}