如何找到 Java8 方法引用的目标?
How can I find the target of a Java8 method reference?
我想捕获对模拟对象的调用
public interface Service {
public String stringify(Object o);
}
service = mockery.mock(Service.class);
mockery.allowing(service::stringify).with(42).will(() -> "42");
所以里面 allowing
我有一个 Function<Object, String>
是否有任何反射魔术可以让我从方法引用创建的函数中找到服务?
public WithClause allowing(Function<T,R> f) {
Object myServiceBackAgain = findTargetOf(function);
....
}
我知道 Function 总是来自这些方法引用,所以我很乐意根据需要进行向下转换。
这与相关的 Is it possible to convert method reference to MethodHandle? 不是同一个问题,因为首先它不是同一个问题,只是在相关领域。而且即使我可以获得一个 MethodHandle,我也无法从中获取目标。
没有找到目标的直接方法,因为方法引用只是在幕后被翻译成 lambda(根据定义,它是匿名的)。所以你需要使用一个解决方法。
想必您熟悉 Java 7 的代理,因为您已经成功实现了 mock
工厂方法。
解决方法是当有人调用你的 allowing
方法时,你设置某种全局标志来提醒你的所有模拟你想要记录下一个调用,然后你调用你的 lambda给出。通过查看哪个 mock 记录了调用,您现在已经找到了方法引用的目标,您可以取消设置全局标志并继续您的 mocking 框架的其余部分。
这很丑,我知道。
使用this SO post中的技巧你可以找到目标。下面的重要方法是findTarget
。事实证明,lambda 确实捕获了它们的目标,您可以从 SerializedLambda
.
访问它们
但是,这是一个非常讨厌的反射 hack,它可能会在未来的版本中被破坏。我不容忍它的使用。
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.function.Function;
public class FindMethodReferenceTarget {
public static void main(String[] args) {
String s = "123";
Optional<Object> target = findTarget(s::charAt);
System.out.println(target.get().equals(s));
Object o = new FindMethodReferenceTarget();
target = findTarget(o::equals);
System.out.println(target.get().equals(o));
}
private static <T, R> Optional<Object> findTarget(
DebuggableFunction<T, R> methodReference) {
return getLambda(methodReference).map(l -> l.getCapturedArg(0));
}
private static Optional<SerializedLambda> getLambda(Serializable lambda) {
for (Class<?> cl = lambda.getClass(); cl != null; cl = cl.getSuperclass()) {
try {
Method m = cl.getDeclaredMethod("writeReplace");
m.setAccessible(true);
Object replacement = m.invoke(lambda);
if (!(replacement instanceof SerializedLambda)) {
break; // custom interface implementation
}
SerializedLambda l = (SerializedLambda) replacement;
return Optional.of(l);
} catch (NoSuchMethodException e) {
// do nothing
} catch (IllegalAccessException | InvocationTargetException e) {
break;
}
}
return Optional.empty();
}
@FunctionalInterface
private static interface DebuggableFunction<T, R> extends
Serializable,
Function<T, R> {}
}
我想捕获对模拟对象的调用
public interface Service {
public String stringify(Object o);
}
service = mockery.mock(Service.class);
mockery.allowing(service::stringify).with(42).will(() -> "42");
所以里面 allowing
我有一个 Function<Object, String>
是否有任何反射魔术可以让我从方法引用创建的函数中找到服务?
public WithClause allowing(Function<T,R> f) {
Object myServiceBackAgain = findTargetOf(function);
....
}
我知道 Function 总是来自这些方法引用,所以我很乐意根据需要进行向下转换。
这与相关的 Is it possible to convert method reference to MethodHandle? 不是同一个问题,因为首先它不是同一个问题,只是在相关领域。而且即使我可以获得一个 MethodHandle,我也无法从中获取目标。
没有找到目标的直接方法,因为方法引用只是在幕后被翻译成 lambda(根据定义,它是匿名的)。所以你需要使用一个解决方法。
想必您熟悉 Java 7 的代理,因为您已经成功实现了 mock
工厂方法。
解决方法是当有人调用你的 allowing
方法时,你设置某种全局标志来提醒你的所有模拟你想要记录下一个调用,然后你调用你的 lambda给出。通过查看哪个 mock 记录了调用,您现在已经找到了方法引用的目标,您可以取消设置全局标志并继续您的 mocking 框架的其余部分。
这很丑,我知道。
使用this SO post中的技巧你可以找到目标。下面的重要方法是findTarget
。事实证明,lambda 确实捕获了它们的目标,您可以从 SerializedLambda
.
但是,这是一个非常讨厌的反射 hack,它可能会在未来的版本中被破坏。我不容忍它的使用。
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.function.Function;
public class FindMethodReferenceTarget {
public static void main(String[] args) {
String s = "123";
Optional<Object> target = findTarget(s::charAt);
System.out.println(target.get().equals(s));
Object o = new FindMethodReferenceTarget();
target = findTarget(o::equals);
System.out.println(target.get().equals(o));
}
private static <T, R> Optional<Object> findTarget(
DebuggableFunction<T, R> methodReference) {
return getLambda(methodReference).map(l -> l.getCapturedArg(0));
}
private static Optional<SerializedLambda> getLambda(Serializable lambda) {
for (Class<?> cl = lambda.getClass(); cl != null; cl = cl.getSuperclass()) {
try {
Method m = cl.getDeclaredMethod("writeReplace");
m.setAccessible(true);
Object replacement = m.invoke(lambda);
if (!(replacement instanceof SerializedLambda)) {
break; // custom interface implementation
}
SerializedLambda l = (SerializedLambda) replacement;
return Optional.of(l);
} catch (NoSuchMethodException e) {
// do nothing
} catch (IllegalAccessException | InvocationTargetException e) {
break;
}
}
return Optional.empty();
}
@FunctionalInterface
private static interface DebuggableFunction<T, R> extends
Serializable,
Function<T, R> {}
}