AspectJ 关于 lambda 表达式的建议:知道 lambda 表达式从哪里来
AspectJ advice on lambda expression: know where the lambda expression came from
给定一个包含两个 lambda 表达式的流:
Stream.of(new String[]{"a", "b"})
.map(s -> s.toUpperCase())
.filter(s -> s.equals("A"))
.count();
和匹配所有 lambda 的 AspectJ 建议(取自 )并打印出被调用方法的名称和 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[=12=](String)] with parameter [a]
lambda called: [boolean aspectj.Starter.lambda(String)] with parameter [A]
lambda called: [String aspectj.Starter.lambda[=12=](String)] with parameter [b]
lambda called: [boolean aspectj.Starter.lambda(String)] with parameter [B]
有没有办法在输出中不仅包含 lambda 的参数,还包含将 lambda 作为参数的 Stream 方法?换句话说:是否可以在 beforeLambda
方法中知道当前是否正在处理 map
或 filter
调用?
我正在寻找的输出是:
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[=20=]
用于映射,lambda
用于过滤器),但由于它们是由编译器生成的,因此无法在代码中使用此信息。我可以尝试根据 return 类型来区分这两种情况,但在我的现实生活中,不同的 lambda 表达式也具有相同的 return 类型。
- 尝试找到一个更具体的切入点表达式,它只匹配一个调用。同样,问题是无法知道为 map 或 filter lambda 生成的方法的名称。
- 查看堆栈跟踪,而
beforeLambda
是 运行。在这两种情况下,堆栈跟踪中的最低条目是流的 count
方法,beforeLambda
之前的最后一个条目是生成的方法:
at aspectj.Starter$LambdaAspect.beforeLambda(Starter.java:25)
at aspectj.Starter.lambda[=14=](Starter.java:14)
at java.util.stream.ReferencePipeline.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 方法(在
map
和 filter
其中一个 lambda 的情况下)以便我稍后可以替换输出中生成的方法名称。但是,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/1112743104@512c45e7])
Stream method called: [filter] with parameter [aspectj.Starter$$Lambda/888074880@75e9a87])
Stream method called: [count] with parameter [null])
lambda called: [String aspectj.Starter.lambda[=16=](String)] with parameter [a]
lambda called: [boolean aspectj.Starter.lambda(String)] with parameter [A]
lambda called: [String aspectj.Starter.lambda[=16=](String)] with parameter [b]
lambda called: [boolean aspectj.Starter.lambda(String)] with parameter [B]
不拦截 lambda execution()
而是 call()
到 Java 流方法怎么样? (这里不能使用执行,因为 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
更新: 如果您切换到本机 AspectJ 语法——出于多种原因,我认为它更具可读性和优雅性,例如因为您可以在切入点中使用导入的 classes 而无需完全限定包名称 - 您可以将 thisEnclosingJoinPointStaticPart
用于 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
在您的情况下 - 启动之前的非终端函数链。
- 我上面所说的并没有因为也有并行流而变得更复杂。执行顺序不一定是线性的。
因此,即使您在 classes 中显式实现功能接口而不是使用 lambda,也是如此。但是至少你可以从日志中的 class 名称推断出发生了什么:
修改后的驱动程序应用:
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
@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()
被调用后,所有连接 之前 的函数才会被执行。
给定一个包含两个 lambda 表达式的流:
Stream.of(new String[]{"a", "b"})
.map(s -> s.toUpperCase())
.filter(s -> s.equals("A"))
.count();
和匹配所有 lambda 的 AspectJ 建议(取自
@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[=12=](String)] with parameter [a]
lambda called: [boolean aspectj.Starter.lambda(String)] with parameter [A]
lambda called: [String aspectj.Starter.lambda[=12=](String)] with parameter [b]
lambda called: [boolean aspectj.Starter.lambda(String)] with parameter [B]
有没有办法在输出中不仅包含 lambda 的参数,还包含将 lambda 作为参数的 Stream 方法?换句话说:是否可以在 beforeLambda
方法中知道当前是否正在处理 map
或 filter
调用?
我正在寻找的输出是:
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[=20=]
用于映射,lambda
用于过滤器),但由于它们是由编译器生成的,因此无法在代码中使用此信息。我可以尝试根据 return 类型来区分这两种情况,但在我的现实生活中,不同的 lambda 表达式也具有相同的 return 类型。 - 尝试找到一个更具体的切入点表达式,它只匹配一个调用。同样,问题是无法知道为 map 或 filter lambda 生成的方法的名称。
- 查看堆栈跟踪,而
beforeLambda
是 运行。在这两种情况下,堆栈跟踪中的最低条目是流的count
方法,beforeLambda
之前的最后一个条目是生成的方法:
at aspectj.Starter$LambdaAspect.beforeLambda(Starter.java:25)
at aspectj.Starter.lambda[=14=](Starter.java:14)
at java.util.stream.ReferencePipeline.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 方法(在
map
和filter
其中一个 lambda 的情况下)以便我稍后可以替换输出中生成的方法名称。但是,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/1112743104@512c45e7])
Stream method called: [filter] with parameter [aspectj.Starter$$Lambda/888074880@75e9a87])
Stream method called: [count] with parameter [null])
lambda called: [String aspectj.Starter.lambda[=16=](String)] with parameter [a]
lambda called: [boolean aspectj.Starter.lambda(String)] with parameter [A]
lambda called: [String aspectj.Starter.lambda[=16=](String)] with parameter [b]
lambda called: [boolean aspectj.Starter.lambda(String)] with parameter [B]
不拦截 lambda execution()
而是 call()
到 Java 流方法怎么样? (这里不能使用执行,因为 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
更新: 如果您切换到本机 AspectJ 语法——出于多种原因,我认为它更具可读性和优雅性,例如因为您可以在切入点中使用导入的 classes 而无需完全限定包名称 - 您可以将 thisEnclosingJoinPointStaticPart
用于 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
在您的情况下 - 启动之前的非终端函数链。 - 我上面所说的并没有因为也有并行流而变得更复杂。执行顺序不一定是线性的。
因此,即使您在 classes 中显式实现功能接口而不是使用 lambda,也是如此。但是至少你可以从日志中的 class 名称推断出发生了什么:
修改后的驱动程序应用:
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
@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()
被调用后,所有连接 之前 的函数才会被执行。