你为什么要把简单的事情复杂化?用户 JB Nizet 已经告诉过你:
获取供应商返回的对象类型的唯一方法是调用供应商。
您自己的代码以一种非常人为的方式(在我修复它以使其编译后,因为它有错误)使用反射。只需通过 args() 使用 AspectJ 参数参数绑定并使其类型安全。如果您只想记录Supplier 的返回值而不影响方法执行,也可以使用@Before 而不是@Around。这样您就可以避免调用ProceedingJoinPoint.proceed(),这是您的“解决方案”示例代码中必不可少但完全缺失的东西。
这个小MCVE怎么样?
package de.scrum_master.app;
public class MyCustomClass {}
package de.scrum_master.app;
import java.util.function.Supplier;
public class A {
public void foo(Supplier<?> msg) {}
}
package de.scrum_master.app;
public class B {
public void bar() {
A a = new A();
a.foo(() -> new MyCustomClass());
}
public static void main(String[] args) {
new B().bar();
}
}
package de.scrum_master.aspect;
import java.util.function.Supplier;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MyAspect {
@Before("execution(* *(*)) && args(supplier)")
public void methodCallWithSupplierArgument(JoinPoint thisJoinPoint, Supplier<?> supplier) throws Exception {
System.out.println(thisJoinPoint + " -> " + supplier.get());
}
}
运行B.main(..)时的控制台日志:
execution(void de.scrum_master.app.A.foo(Supplier)) -> de.scrum_master.app.MyCustomClass@66a29884
这与你的方面试图做的一样,只是更干净。我认为它肯定也更具可读性。
警告:如果供应商有副作用或计算成本高,请在致电get() 之前三思而后行。我知道纯函数(即用 Java 语言实现函数式接口的代码)永远不会有任何副作用,但如果以糟糕的风格编码,它们很容易做到。所以要小心。
更新:说到副作用的警告,让我给你看点东西。只需稍微扩展应用程序代码,以便实际评估供应商并(可选)返回其结果:
package de.scrum_master.app;
import java.util.function.Supplier;
public class A {
public Object foo(Supplier<?> msg) {
return msg.get();
}
}
现在还让我们扩展切面,以便在调用供应商的get() 方法时实际触发日志记录:
package de.scrum_master.aspect;
import java.util.function.Supplier;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MyAspect {
@Before("execution(* *(*)) && args(supplier)")
public void methodCallWithSupplierArgument(JoinPoint thisJoinPoint, Supplier<?> supplier) throws Exception {
System.out.println(thisJoinPoint + " -> " + supplier.get());
}
@AfterReturning(pointcut = "call(public * java.util.function.Supplier+.get())", returning = "result")
public void supplierEvaluated(JoinPoint thisJoinPoint, Object result) throws Exception {
System.out.println(thisJoinPoint + " -> " + result);
}
}
现在控制台日志将是:
call(Object java.util.function.Supplier.get()) -> de.scrum_master.app.MyCustomClass@66a29884
execution(Object de.scrum_master.app.A.foo(Supplier)) -> de.scrum_master.app.MyCustomClass@66a29884
call(Object java.util.function.Supplier.get()) -> de.scrum_master.app.MyCustomClass@4769b07b
你能看到Supplier.get()被调用两次,返回两个不同的MyCustomClass对象,即MyCustomClass@66a29884和MyCustomClass@4769b07b吗?这是因为应用程序和第一方面的建议都调用了get()。后者并不会真正记录与应用程序创建的对象相同的对象,因此即使没有进一步的副作用,您也会记录错误的内容并执行供应商方法两次而不是一次。
所以让我们通过不再从第一个通知方法调用get() 来清理它:
@Before("execution(* *(*)) && args(supplier)")
public void methodCallWithSupplierArgument(JoinPoint thisJoinPoint, Supplier<?> supplier) throws Exception {
System.out.println(thisJoinPoint + " -> " + supplier); // no 'get()' call anymore
}
现在日志变得干净了:
execution(Object de.scrum_master.app.A.foo(Supplier)) -> de.scrum_master.app.B$$Lambda$1/1349393271@66a29884
call(Object java.util.function.Supplier.get()) -> de.scrum_master.app.MyCustomClass@4769b07b
另一个优点是现在get() 结果会在方法被真正调用时被记录(可以同步、异步、多次或从不),而不是当切面冗余执行它时。
P.S.:如果您想知道为什么我如此谨慎地不执行 get() 只是为了记录目的,想象一下 lambda 打开一个数据库连接,创建一个 4 GB 文件,下载一个持续时间为 90 分钟的 4K 视频或任何。它将执行两次,以便让您记录它。