java8 lambda 参数类型转换为对象

java8 lambda argument type conversion to object

我正在尝试将 lambda 参数强制转换为 class 类型,但是我总是遇到 classcast 异常。这是我要实现的用例。

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

在我的 AOP 运行时 class 我总是得到 foo() 的参数类型为 B$$lambda$0/13qdwqd

问题 我如何获得实际的 class 类型的方法供应商参数(在这种情况下 MyCustomObject?

看点代码

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]);
 }

}

提前致谢!

谢谢大家的评论。我能够通过在 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");
      }
    }

 }

}

为什么要把简单的事情搞复杂?用户 JB Nizet 已经告诉你:

The only way to get the type of the object returned by the supplier is to call the supplier.

您自己的代码使用反射以非常人为的方式执行此操作(在我修复它以使其编译后因为它有错误)。只需通过 args() 使用 AspectJ argument 参数绑定并使其类型安全。如果您只想记录 Supplier 的 return 值而不影响方法执行,也可以使用 @Before 而不是 @Around。这样您就可以避免调用 ProceedingJoinPoint.proceed(),这是您的 "solution" 示例代码中必要但完全缺失的东西。

这个小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 中实现函数接口的代码)不应该有任何副作用,但如果以糟糕的风格编码,它们很容易产生。所以要小心。


更新: 说到副作用的警告,让我给你看一些东西。只需稍微扩展应用程序代码,以便实际评估供应商和(可选)return 其结果:

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() 是如何被调用两次的吗,returning 两个不同的 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/1349393271@66a29884
call(Object java.util.function.Supplier.get()) -> de.scrum_master.app.MyCustomClass@4769b07b

另一个优点是,现在 get() 只要真正调用方法(可以同步、异步、多次或从不执行)而不是在方面冗余执行它时,就会记录结果。

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