使用 java 8 lambda 表达式打印有关错误的调试信息

2022-09-04 01:08:55

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

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

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

我知道我不能在提供的 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

编辑:我知道反射对lambdas没有帮助。我知道AOP,我知道这也可以用纯粹的反射来制作,但我想知道是否有更好的方法来完成Java 8,而Java 7不存在。在我看来,这是应该的。现在,可以执行诸如将 setter 方法传递给另一个 setter 方法之类的操作。


答案 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"


答案 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的返回值。