【问题标题】:java8 lambda argument type conversion to objectjava8 lambda参数类型转换为对象
【发布时间】:2020-01-13 18:50:54
【问题描述】:

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

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

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

问题我如何获得方法供应商参数的实际类类型(在这种情况下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]);
 }

}

提前致谢!

【问题讨论】:

  • 你能给出实际产生异常的代码吗?
  • 你得到你想要的。 B$$lambda$0/13qdwqdSupplier 的实现,不,如果不以某种方式修改代码,您将无法获得 MyCustomObject.class
  • @luk2302 我添加了出现错误的方面代码。
  • @Arajit 你实际上想得到什么?您要解决的具体问题是什么?
  • 当然是。

标签: java aop aspectj aspect lambda


【解决方案1】:

你为什么要把简单的事情复杂化?用户 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() 结果会在方法被真正调用时被记录(可以同步、异步、多次或从不),而不是当切面冗余执行它时。

P.S.:如果您想知道为什么我如此谨慎地不执行 get() 只是为了记录目的,想象一下 lambda 打开一个数据库连接,创建一个 4 GB 文件,下载一个持续时间为 90 分钟的 4K 视频或任何。它将执行两次,以便让您记录它。

【讨论】:

  • 感谢您的详细解释。关于我原始帖子中的代码 sn-p,我不知道代码必须是可编译的。为此道歉。关于您的解决方案,它仍然没有解决我的问题。我仍然无法使用方面@Before("execution(* org.apache.logging.log4j.Logger.*(*)) &amp;&amp; args(supplier)") 捕获供应商论点。我的方面根本没有开火。如果我从切入点表达式中删除供应商,那么我的方面就会执行。有了这个,当我打印出参数时,我仍然将参数作为 classes$$lambda$@132e434.
  • 请注意,org.apache.logging.log4j.Logger 是来自 log4j2 库的接口。正如我之前提到的,我要解决的问题是捕获已作为 java8 供应商传递的对象,例如 log.info(()-&gt;new MyCustomClass())
  • 为什么您对您的代码应该编译感到惊讶?如果您发布损坏或伪代码,没有人可以真正帮助您。请注意了解MCVE 是什么,然后通过编辑您的问题或通过在 GitHub 上推送一个小项目来向我展示一个,然后通过评论通知我。使用您之前的 cmets 中提供的小代码 sn-ps,我无法重现和分析您的问题。
  • 此外,我的回答完全符合您的要求。您一定犯了一个错误,使其适应您的需求,例如将供应商添加到切入点,但忘记将其作为参数添加到建议方法签名中。不管是什么,我都看不到,也讨厌推测。
  • 导入正确的类怎么样?而不是导入java.util.function.Supplier,Log4J 使用自己的org.apache.logging.log4j.util.Supplier。如果您替换导入,它将起作用。
【解决方案2】:

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

 }

}

【讨论】:

  • 首先,您的班级缺少@Aspect 注释。其次,您的 @Around 建议没有返回任何内容,因此它永远不会像这样编译。那么现在在您创建了供应商参数类型的实例之后,您将如何处理它?您绝对不能返回它,因为该方法应该返回一个供应商,而不是要提供的对象类型的实例。请解释您认为这样做会获得什么。该代码对我来说没有任何意义,而且您似乎并不真正了解 lambda 的用途。
  • 此外,您的类名 MyCustomObjMyCustomClass 不匹配。然后你的行 Object obj = method.invoke(obj, null); 既没有意义也没有编译。最后,你怎么能期望在没有异常处理的情况下迭代具有相同参数集的类的所有方法。这不是一个解决方案,它只会造成更多的混乱。因此,请更新您的问题和答案,以保持一致并解释您实际尝试做的事情以及如何实现它。
猜你喜欢
  • 1970-01-01
  • 2021-08-11
  • 1970-01-01
  • 2021-12-19
  • 2023-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多