【问题标题】:How can I safely proxy a lambda when I don't know whether it's a lambda up-front?当我不知道它是否是预先的 lambda 时,如何安全地代理 lambda?
【发布时间】:2014-10-31 18:12:49
【问题描述】:

我们有一些遗留的反射代理生成代码,如果你把它看成一个黑盒子,它们基本上是这样工作的:

Object someObject = new Anything();
Object debugObject = ProxyUtils.wrapWithDebugLogging(someObject);

wrapWithDebugLogging 接受任何对象并覆盖它可以的任何方法(如果你正在扩展一个真实的类,最终方法显然是无法修复的),拦截它以记录有关调用的消息,然后调用真实的方法。

在内部,它使用 cglib 来完成工作,并且在构造代理之前有一些保护性逻辑,因为匿名类是最终的,但可以通过使用它们实现的超类或单个接口来处理:

Class<?> clazz = someObject.getClass();
Class<?> interfaces = clazz.getInterfaces();

// Anonymous classes are final so you can't extend them, but we know they only have one
// superclass or one interface.
if (clazz.isAnonymousClass()) {
    clazz = interfaces.length > 0 ?
            interfaces[0] : primaryType.getSuperclass();
}

Enhancer enhancer = new Enhancer();

if (clazz.isInterface()) {
    interfaces.add(clazz);
} else {
    enhancer.setSuperclass(primaryType);
}
enhancer.setInterfaces(interfaces.toArray(new Class[interfaces.size()]));

问题在于 Java 8 的“lambda”类为 isAnonymousClass() 返回 false。但我们希望将它们与匿名类完全相同。

pointed out before 一直没有办法确定一个类是“设计”的 lambda 类。但这在我看来更像是反射 API 中缺少的东西,而且这肯定不是 Java 第一次“忘记”向新 API 添加明显的东西。

那么有没有一种明智的方法来区分 lambda 和非 lambda,而无需在 API 中提供此功能?我可以看到 isSynthetic() 返回 true,但它也可能对各种其他事物返回 true。

【问题讨论】:

  • 为什么不检查Class 是否为finalModifier.isFinal(..)
  • 如果我们对所有 final 类都使用了这个技巧,你可以找到一个 final 类,该类具有在 final 类本身上定义的 final 方法。在这种情况下,该方法不会由代理实现,因此如果有人调用它,您将收到 NoSuchMethodError。出于这个原因,我们对允许通过哪些最终课程持保守态度。也许这意味着我们应该只检查所有方法都存在于超类或接口之一上的最终类。我不确定这是否是一个好的启发式方法。
  • 不,它没有被“遗忘”。它被故意遗漏了,因为它会违反语言的抽象。
  • @BrianGoetz 忽略它违反了反射 API。所以你赢了一些,你也输了一些。
  • 这没有意义。你不能继承 any final 类,所以问final 类是否有final 方法是没有意义的。您不能覆盖其中任何一个。另一方面,当继承超类或实现final 类的接口时,您可以重新声明方法,而不管当时不相关的final 类是否将它们声明为final。如果您坚持自己的逻辑,则很容易构建既不是匿名也不是 lambda 芽的示例仍然会破坏您的代码。

标签: java reflection lambda java-8 cglib


【解决方案1】:

您不应根据是否为 ​​lambda 生成类的问题来创建条件代码。毕竟,只有类的属性很重要。

类是final,所以你不能继承它。即使不是final,子类也是不可能的,因为它只有private 构造函数。它实现了interface。这些是类的相关属性。

在没有任何 lambda 表达式的情况下遇到相同的场景并非不现实:

final class NotALambda implements Function<String,String> {

    public static final Function<String,String> INSTANCE=new NotALambda();

    private NotALambda() {}

    public String apply(String t) {
        return t.toLowerCase();
    }
}

为什么要将此类与通过
Function&lt;String,String&gt; f=String::toLowerCase; 生成的类区别对待?它具有相同的属性和创建代理的相同障碍。在您所说的 cmets 中,您希望根据该方法是否声明为 final 的问题有所作为。这更没有意义,因为我可以在上面示例中的方法中添加 final 修饰符,而无需更改任何内容,无论是语义还是创建代理时将面临的困难。

【讨论】:

    【解决方案2】:

    我不认为这个限制(如果你想这样称呼它)应该是一个问题。

    如果您在适当的情况下对接口进行了适当的编程,则无需让您的代理具有超类(Object 除外)。在可用时设置代理的超类(没有private 构造函数,不是final 等)。

    在所有情况下,您需要做的就是捕获代理对象、目标、拦截其上的所有方法调用、进行日志记录,然后将调用委托或路由给它。

    public static Object wrapWithDebugLogging(Object target) {
        ... // prepare the enhancer as described above
        enhancer.setCallback(new MethodInterceptor() {        
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                logger.debug("Some useful logging message.");
                return method.invoke(target, args);            
            }
        });
        return enhancer.create();
    }
    

    调用的方法是否为final 无关紧要,你不是要覆盖它,你只是在拦截它。

    【讨论】:

    • 许多第三方库(或者在许多情况下甚至是 Java 运行时本身)都没有对接口进行编程。我意识到在所有情况下,我们都可以只引入一个包含所有方法的接口,然后编写一个委托来传递调用,但是如果我们想编写一个新接口并在每次我们只想调试某个方法时委托实现类调用时,我们只需编写该接口的另一个实现来执行调试日志记录。作为代理执行此操作的目的是您只需编写一次逻辑。
    • 另外,如果我们只需要处理接口,我们就不会使用 cglib。因为它实际上比使用 java.lang.reflect.Proxy 慢。 :(
    • @Trejkaz 当然。当超类可用时,使用它。从 lambda 表达式创建的实例的类是 final。像对待任何其他 final 类一样对待它。
    【解决方案3】:

    以下是快速实验的结果。 foo 方法只输出其参数的getClass().toString()。 (getClass().getName() 输出相同的结果。)

        foo (new Runnable() {
                @Override
                public void run() {
                    System.out.println("run");
                }
             });
    

    输出:Test32$1

        foo (() -> System.out.println("run"));
    

    输出:Test32$$Lambda$1/640070680

    不保证它的便携性。

    【讨论】:

    • 该输出可能会在将来或通过编译器或 JVM 实现发生变化。不要依赖它。
    • 我也想过 toString() hack,但是 JRE 的未来版本可能会破坏它。话又说回来,JRE 的未来版本也破坏了 isAnonymousClass(),所以谁知道什么是安全的......
    • @Trejkaz 是的,我认为 lambda 是匿名类的简写,所以我很惊讶地发现 isAnonymousClass 返回了 false
    • @ajb 和 Trejkaz -- Lambda 不是匿名类的简写。它们的语义和表示是不同的。
    • 这是一个非常简单的故事:它们是功能接口的实例。该实例是如何创建的——通过实例化一个命名类、一个匿名类、一个动态代理、一个 lambda 表达式、一团仙尘——是(并且应该是!)隐藏在“接口”的抽象背后。跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-28
    • 2016-05-12
    • 1970-01-01
    • 2023-03-05
    • 2016-04-11
    相关资源
    最近更新 更多