Java8 Lambda 参数类型转换为对象



我正在尝试将lambda参数类型转换为类类型,但是我总是遇到类广播异常。这是我试图实现的用例。

  1. 类 A 有一个方法public void foo(Supplier<?> msg)
  2. 我在 foo(( 方法上放了一个Apsect来捕获参数(在这种情况下是 msg 的实际值(
  3. 类 B 使用类 A 的实例使用 lambda 表达式调用 foo(( 方法
class B{
public void bar(){
A a=new A()
a.foo(()->{ new MyCustomObject()});
}
}

在我的 AOP 类的运行时,我总是得到 foo(( 的 argurment 类型为B$$lambda$0/13qdwqd

问题如何获取方法供应商参数的实际类类型(在本例中为我的自定义对象

方面代码

Class MyAspect{
@Around("call(public void com.mypkg.A.foo(..))")
public Object captureLambdaType(final ProceedingJoinPoint pjp) throws Throwable {
System.out.println("lambda called: [" + pjp.getSignature() + "] "+
"with parameter [" + pjp.getArgs()[0] + "]");
Supplier<MyCustomObject> obj= (Supplier<MyCustomObject> )(pjp.getArgs()[0]);
}
}

提前感谢!

你为什么要把一件简单的事情变得复杂?用户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@66a29884MyCustomClass@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()结果。

PS:如果你想知道为什么我如此一丝不苟地不执行get()只是为了记录目的,想象一下lambda打开数据库连接,创建一个4 GB的文件,下载一个持续时间为90分钟的4K视频或其他什么。它会做两次,只是为了让你记录它。

谢谢大家的评论。我能够通过在 lambda 上调用get()方法来解决它。示例代码如下


Class MyAspect{
@Around("call(public void com.mypkg.A.foo(..))")
public Object captureLambdaType(final ProceedingJoinPoint pjp) throws Throwable {
System.out.println("lambda called: [" + pjp.getSignature() + "] "+
"with parameter [" + pjp.getArgs()[0] + "]");
//Get the lambda argument
Object arg=pjp.getArgs()[0];
//Get the argument class type
Class clazz=arg.getClass();
for (Method method : clazz.getDeclaredMethods()) {
method.setAccessible(true);
Object obj=method.invoke(obj,null);
if(obj instanceof MyCustomClass){
MyCustomClass myObject= (MyCustomClass) obj;
System.out.println("Hurray");
}
}
}
}

相关内容

  • 没有找到相关文章

最新更新