s.toUpperCase()) .filter(s -6ren">
gpt4 book ai didi

java - AspectJ 关于 lambda 表达式的建议 : know where the lambda expression came from

转载 作者:行者123 更新时间:2023-12-02 11:38:10 25 4
gpt4 key购买 nike

给定一个具有两个 lambda 表达式的流:

Stream.of(new String[]{"a", "b"})
.map(s -> s.toUpperCase())
.filter(s -> s.equals("A"))
.count();

以及一个匹配所有 lambda 的 AspectJ 建议(取自 here )并打印出被调用方法的名称和 lamdba 第一个参数的值:

@Before("execution(* *..*lambda*(..))")
public void beforeLambda(JoinPoint jp) {
System.out.println("lambda called: [" + jp.getSignature() + "] "+
"with parameter [" + jp.getArgs()[0] + "]");
}

输出为:

lambda called: [String aspectj.Starter.lambda$0(String)] with parameter [a]
lambda called: [boolean aspectj.Starter.lambda$1(String)] with parameter [A]
lambda called: [String aspectj.Starter.lambda$0(String)] with parameter [b]
lambda called: [boolean aspectj.Starter.lambda$1(String)] with parameter [B]

有没有办法在输出中不仅包含 lambda 的参数,而且还包含以 lambda 作为参数的 Stream 方法?换句话说:是否可以在 beforeLambda 方法中知道当前是否正在处理 mapfilter 调用?

我正在寻找的输出是:

lambda called: [map] with parameter [a]
lambda called: [filter] with parameter [A]
lambda called: [map] with parameter [b]
lambda called: [filter] with parameter [B]

<小时/>到目前为止我已经尝试过:

  • 检查JoinPoint中的信息。它包含由 lambda 表达式创建的方法的签名。实际方法的名称是不同的(映射的lambda$0和过滤器的lambda$1),但由于它们是由编译器生成的,所以无法使用它代码中的信息。我可以尝试根据返回类型来区分这两种情况,但在我的现实生活问题中,不同的 lambda 表达式也具有相同的返回类型。
  • 尝试找到一个仅与其中一个调用匹配的更具体的切入点表达式。同样,问题是无法知道为映射或过滤器 lambda 生成的方法的名称。
  • beforeLambda 运行时查看堆栈跟踪。在这两种情况下,堆栈跟踪中的最低条目是流的 count 方法,而 beforeLambda 之前的最后一个条目是生成的方法:
at aspectj.Starter$LambdaAspect.beforeLambda(Starter.java:25)
at aspectj.Starter.lambda$0(Starter.java:14)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
[...more from java.util, but no hint to map or filter...]
at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526)
at aspectj.Starter.main(Starter.java:16)
  • 向 Stream 方法添加第二个方面,打印出使用哪个参数调用哪个 Stream 方法(在使用 lambda 之一的 mapfilter 的情况下) )以便我稍后可以替换输出中生成的方法名称。但是,Stream 方法中的 lambda 名称与 beforeLambda 输出中看到的方法名称不匹配:
@Before("call(* java.util.stream.Stream.*(..))")
public void beforeStream(JoinPoint jp) {
System.out.println("Stream method called: [" + jp.getSignature().getName() + "] with parameter [" + (jp.getArgs().length > 0 ? jp.getArgs()[0] : "null") + "])");
}
Stream method called: [of] with parameter [[Ljava.lang.String;@754c89eb])
Stream method called: [map] with parameter [aspectj.Starter$$Lambda$1/1112743104@512c45e7])
Stream method called: [filter] with parameter [aspectj.Starter$$Lambda$2/888074880@75e9a87])
Stream method called: [count] with parameter [null])
lambda called: [String aspectj.Starter.lambda$0(String)] with parameter [a]
lambda called: [boolean aspectj.Starter.lambda$1(String)] with parameter [A]
lambda called: [String aspectj.Starter.lambda$0(String)] with parameter [b]
lambda called: [boolean aspectj.Starter.lambda$1(String)] with parameter [B]

最佳答案

不拦截 lambda execution() 而是拦截 Java 流方法的 call() 怎么样? (此处无法使用执行,因为 AspectJ 无法拦截 JDK 方法执行,因为它们位于代码库之外。)

驱动程序应用程序:

package de.scrum_master.app;

import java.util.stream.Stream;

public class Application {
public static void main(String[] args) {
new Application().doSomething();
}

public long doSomething() {
return Stream.of(new String[]{"a", "b"})
.map(s -> s.toUpperCase())
.filter(s -> s.equals("A"))
.count();
}
}

方面:

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.SourceLocation;

@Aspect
public class MyAspect {
@Before("!within(*Aspect) && call(* java.util.stream.Stream.*(..))")
public void interceptStreamMethods(JoinPoint thisJoinPoint) throws Throwable {
System.out.println(thisJoinPoint);
SourceLocation sourceLocation = thisJoinPoint.getSourceLocation();
System.out.println(" " + sourceLocation.getWithinType());
System.out.println(" " + sourceLocation.getFileName());
System.out.println(" " + sourceLocation.getLine());
}
}

如您所见,我还添加了源位置信息以用于演示目的。如果你问我,我不会使用它,我只是想向你展示它的存在。

控制台日志:

call(Stream java.util.stream.Stream.of(Object[]))
class de.scrum_master.app.Application
Application.java
11
call(Stream java.util.stream.Stream.map(Function))
class de.scrum_master.app.Application
Application.java
12
call(Stream java.util.stream.Stream.filter(Predicate))
class de.scrum_master.app.Application
Application.java
13
call(long java.util.stream.Stream.count())
class de.scrum_master.app.Application
Application.java
14
<小时/>

更新:如果您切换到 native AspectJ 语法 - 我认为出于多种原因,它更具可读性和优雅性,例如因为您可以在切入点中使用导入的类,而无需完全限定包名称 - 您可以将 thisEnendingJoinPointStaticPart 用于 call() 切入点,如下所示:

修改方面:

package de.scrum_master.aspect;

import java.util.stream.Stream;

public aspect MyAspect {
before(): !within(*Aspect) && call(* Stream.*(..)) {
System.out.println(thisJoinPoint);
System.out.println(" called by: " + thisEnclosingJoinPointStaticPart);
System.out.println(" line: " + thisJoinPoint.getSourceLocation().getLine());
}
}

新控制台日志:

call(Stream java.util.stream.Stream.of(Object[]))
called by: execution(long de.scrum_master.app.Application.doSomething())
line: 11
call(Stream java.util.stream.Stream.map(Function))
called by: execution(long de.scrum_master.app.Application.doSomething())
line: 12
call(Stream java.util.stream.Stream.filter(Predicate))
called by: execution(long de.scrum_master.app.Application.doSomething())
line: 13
call(long java.util.stream.Stream.count())
called by: execution(long de.scrum_master.app.Application.doSomething())
line: 14
<小时/>

OP 显着改变了他的问题后更新:

你想要的东西是不可能的。原因可以在问题底部您自己的日志输出中看到:

  • 在执行映射函数之前,流方法调用早已完成。不要让源代码的外观欺骗了您。
  • 这是因为 Java 流惰性。仅当调用终端函数时 - 在您的情况下 count - 在该函数启动之前的非终端函数链。
  • 我上面所说的并没有因为存在并行流而变得更加复杂。无论如何,执行顺序不一定是线性的。

因此,即使您在类中显式实现函数式接口(interface)而不是使用 lambda,这也是事实。但至少你可以从日志中的类名推断出发生了什么:

修改后的驱动程序应用程序:

package de.scrum_master.app;

import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

public class Application {
public static void main(String[] args) {
new Application().doSomething();
}

public long doSomething() {
return Stream.of(new String[]{"a", "b"})
.map(new UpperCaseMapper())
.filter(new EqualsAFilter())
.count();
}

static class UpperCaseMapper implements Function<String, String> {
@Override
public String apply(String t) {
return t.toUpperCase();
}
}

static class EqualsAFilter implements Predicate<String> {
@Override
public boolean test(String t) {
return t.equals("A");
}
}
}

修改方面:

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

// See https://stackoverflow.com/a/48778440/1082681

@Aspect
public class MyAspect {
@Before("call(* java.util.stream..*(..))")
public void streamCall(JoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
}

@Before("execution(* java.util.function..*(*)) && args(functionArg)")
public void functionExecution(JoinPoint thisJoinPoint, Object functionArg) {
System.out.println(thisJoinPoint);
System.out.println(" " + thisJoinPoint.getTarget().getClass().getSimpleName() + " -> " + functionArg);
}
}

修改控制台日志:

call(Stream java.util.stream.Stream.of(Object[]))
call(Stream java.util.stream.Stream.map(Function))
call(Stream java.util.stream.Stream.filter(Predicate))
call(long java.util.stream.Stream.count())
execution(String de.scrum_master.app.Application.UpperCaseMapper.apply(String))
UpperCaseMapper -> a
execution(boolean de.scrum_master.app.Application.EqualsAFilter.test(String))
EqualsAFilter -> A
execution(String de.scrum_master.app.Application.UpperCaseMapper.apply(String))
UpperCaseMapper -> b
execution(boolean de.scrum_master.app.Application.EqualsAFilter.test(String))
EqualsAFilter -> B

没有比这更好的了。如果你想要真正理解的日志输出,你需要按照我的方式进行重构。正如我所说:只有在 count() 被调用之后,所有之前连接的函数才会被执行。

关于java - AspectJ 关于 lambda 表达式的建议 : know where the lambda expression came from,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48775705/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com