【问题标题】:Printing debug info on errors with java 8 lambda expressions使用 java 8 lambda 表达式打印错误的调试信息
【发布时间】:2014-02-18 17:19:18
【问题描述】:

我想使用一个静态方法作为 setter 助手来捕获异常并打印有关失败操作的调试信息。我不只想要异常详细信息。我想显示正在设置的属性,以便详细信息有助于快速调试问题。我正在使用 Java 8。

我应该如何提供或检测正在设置的属性?

我希望删除示例中的“名称”字符串并获得相同的结果。

我知道我不能对提供的 setter 方法使用反射,该方法先转换为 lambda 表达式,然后再转换为 BiConsumer。

我收到了,但需要提供属性名称。

/** setter helper method **/
private static <E, V> void set(E o, BiConsumer<E, V> setter,
        Supplier<V> valueSupplier, String propertyName) {
    try {
        setter.accept(o, valueSupplier.get());
    } catch (RuntimeException e) {
        throw new RuntimeException("Failed to set the value of " + propertyName, e);
    }
}

例子:

    Person p = new Person();
    Supplier<String> nameSupplier1 = () ->  "MyName";
    Supplier<String> nameSupplier2 = () -> { throw new RuntimeException(); };
    set(p, Person::setName, nameSupplier1, "name");
    System.out.println(p.getName()); // prints MyName
    set(p, Person::setName, nameSupplier2, "name"); // throws exception with message  Failed to set the value of name
    System.out.println(p.getName()); // Does not execute

编辑:我知道反射对 lambda 没有帮助。我知道 AOP 并且我知道这也可以通过纯反射来实现,但我想知道是否有更好的方法来使用 Java 8 完成此操作,而 Java 7 中不存在。在我看来应该如此。现在可以做一些事情,比如将一个 setter 方法传递给另一个。

【问题讨论】:

  • 我不喜欢这个问题的标题。欢迎提出建议或修改。
  • 我不确定这是否有帮助,但也许它会对调试信息有所帮助:code.google.com/p/java-interceptor/wiki/Documentation
  • 谢谢@Zavior,但我想要一些不那么侵入性的东西,不拦截每个呼叫,而是捕获错误并仅在出现错误时工作。
  • 这是一个奇怪的问题。当不是属性而是Supplier 失败时,它如何帮助解决打印属性名称的问题?如果是失败的属性,它的 setter 方法将包含在堆栈跟踪中。如果不是,那么请专注于失败的事情而不是未涉及的财产。
  • @Holger 它为您提供了一个非常有用的上下文,当失败的不是代码而是输入数据时。如果数据错误,则处理可能会失败,并且错误的数据不足以调试,您需要知道该数据应该是什么,例如是人名还是人的电话号码。供应商可能是一个简单的 lambda,例如 s -&gt; s.substring(1)

标签: java debugging reflection lambda java-8


【解决方案1】:

如果您希望方法引用作为唯一输入,您可以使用以下技巧将它们调试为可打印的名称:

public static void main(String[] args) {
  Person p = new Person();
  Supplier<String> nameSupplier1 = () -> "MyName";
  Supplier<String> nameSupplier2 = () -> { throw new RuntimeException(); };
  set(p, Person::setName, nameSupplier1);
  System.out.println(p.getName()); // prints MyName
  set(p, Person::setName, nameSupplier2); // throws exception with message
  System.out.println(p.getName()); // Does not execute
}

interface DebuggableBiConsumer<A, B> extends BiConsumer<A, B>, Serializable {}

private static <E, V> void set(
    E o, DebuggableBiConsumer<E, V> setter, Supplier<V> valueSupplier) {
  try {
    setter.accept(o, valueSupplier.get());
  } catch (RuntimeException e) {
    throw new RuntimeException("Failed to set the value of "+name(setter), e);
  }
}

private static String name(DebuggableBiConsumer<?, ?> setter) {
  for (Class<?> cl = setter.getClass(); cl != null; cl = cl.getSuperclass()) {
    try {
      Method m = cl.getDeclaredMethod("writeReplace");
      m.setAccessible(true);
      Object replacement = m.invoke(setter);
      if(!(replacement instanceof SerializedLambda))
        break;// custom interface implementation
      SerializedLambda l = (SerializedLambda) replacement;
      return l.getImplClass() + "::" + l.getImplMethodName();
    }
    catch (NoSuchMethodException e) {}
    catch (IllegalAccessException | InvocationTargetException e) {
      break;
    }
  }
  return "unknown property";
}

限制是它会为 lambda 表达式打印不是非常有用的方法引用(对包含 lambda 代码的合成方法的引用)和"unknown property" 用于接口的自定义实现。

【讨论】:

  • SerializedLambda 是保持不同实现之间兼容性的 形式。理论上,只有writeReplace 方法可以用不同的机制替换。如果您想完全安全,则必须真实地编写对象,例如成一个字节数组并解析输出。输出 必须 包含一个 SerializedLambda 用于符合要求的实现。是的,仅在例外情况下它很慢。
  • 你用的是哪个编译器?我发现当前的 eclipse beta 没有正确编译它。我使用了 netbeans 和 jdk-8-fcs-bin-b128。但是我现在用几个版本对其进行了测试。与netbeans 和/或javac 一起使用。对于eclipse,我们必须等待更新的版本......
  • 不,问题是eclipse编译器没有在它应该生成的地方生成Serializable lambda。 writeObject 方法只会抛出 NotSerializableException
  • 我向eclipse团队提交了一份报告;让我们看看修复需要多长时间。这可能是一个简单的解决方案,因为 Serialization 用于 lambda 表达式 ((…) -&gt; …) 已经有效。
  • MethodHandle 非常接近。在字节码级别,它们已经可以像常量(如ClassString 文字)一样使用。使用 Java 8,您可以decode a direct handle。顺便说一句,这是 lambda 表达式/方法参考实现在内部使用的。
【解决方案2】:

那又怎样?

源代码:

package test;

import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;

public class Person {
    public String getName() {
        return "Michael";
    }

    public static void main(String[] args) throws
            NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Serializable s = (Function<Person, String> & Serializable) Person::getName;

        Method method = s.getClass().getDeclaredMethod("writeReplace");
        method.setAccessible(true);
        SerializedLambda serializedLambda = (SerializedLambda) method.invoke(s);

        System.out.println(serializedLambda.getImplClass().replace("/", ".")
                + "::" + serializedLambda.getImplMethodName());
    }
}

输出:

test.Person::getName

您实际上需要查找方法“writeReplace”,调用它并评估其返回值,该值是 SerializedLambda 类型。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-01
    相关资源
    最近更新 更多