java8 lambda 参数类型转换为对象
java8 lambda argument type conversion to object
我正在尝试将 lambda 参数强制转换为 class 类型,但是我总是遇到 classcast 异常。这是我要实现的用例。
- class A有个方法
public void foo(Supplier<?> msg)
- 我在 foo() 方法上放置了一个 Apsect 来捕获参数(在本例中为 msg 的实际值)
- 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@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/1349393271@66a29884
call(Object java.util.function.Supplier.get()) -> de.scrum_master.app.MyCustomClass@4769b07b
另一个优点是,现在 get()
只要真正调用方法(可以同步、异步、多次或从不执行)而不是在方面冗余执行它时,就会记录结果。
P.S.: 如果你想知道为什么我如此小心翼翼地不执行 get()
只是为了记录目的,想象一下 lambda 打开一个数据库连接,创建一个 4 GB 的文件,下载时长 90 分钟或其他时间的 4K 视频。会做两次,就是为了让你记录下来。
我正在尝试将 lambda 参数强制转换为 class 类型,但是我总是遇到 classcast 异常。这是我要实现的用例。
- class A有个方法
public void foo(Supplier<?> msg)
- 我在 foo() 方法上放置了一个 Apsect 来捕获参数(在本例中为 msg 的实际值)
- 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@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/1349393271@66a29884
call(Object java.util.function.Supplier.get()) -> de.scrum_master.app.MyCustomClass@4769b07b
另一个优点是,现在 get()
只要真正调用方法(可以同步、异步、多次或从不执行)而不是在方面冗余执行它时,就会记录结果。
P.S.: 如果你想知道为什么我如此小心翼翼地不执行 get()
只是为了记录目的,想象一下 lambda 打开一个数据库连接,创建一个 4 GB 的文件,下载时长 90 分钟或其他时间的 4K 视频。会做两次,就是为了让你记录下来。