Given a stream with two lambda expressions working on it:
Stream.of(new String[]{"a", "b"})
.map(s -> s.toUpperCase())
.filter(s -> s.equals("A"))
.count();
and an AspectJ advice that matches all lambdas (taken from here ) and prints out the name of the called method and the value of the lamdba's first parameter:
@Before("execution(* *..*lambda*(..))")
public void beforeLambda(JoinPoint jp) {
System.out.println("lambda called: [" + jp.getSignature() + "] "+
"with parameter [" + jp.getArgs()[0] + "]");
}
The output is:
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]
Is there a way to include in the output not only the lambda's parameter, but also the Stream's method that got the lambda as parameter? In other words: is it possible to know in the beforeLambda
method, if currently the map
or the filter
call is being processed?
The output I am looking for would be:
lambda called: [map] with parameter [a]
lambda called: [filter] with parameter [A]
lambda called: [map] with parameter [b]
lambda called: [filter] with parameter [B]
lambda$0
for map and lambda$1
for filter), but as they are generated by the compiler, there is no way to use this information in the code. I could try to distinguish the two cases based on the return types, but in my real life problem, the different lambda expressions also have the same return types. beforeLambda
is running. In both cases, the lowest entry in the stack trace is the stream's count
method and the last entry before beforeLambda
is the generated method: @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") + "])");
}
map
and filter
one of the lambdas) so that I could later replace the generated method names in the output. However the lambda's names in the Stream methods do not match the method names seen in the beforeLambda
output: @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]
How about not intercepting lambda execution()
but call()
to Java stream methods instead? (Cannot use execution here because AspectJ cannot intercept JDK method executions as they are outside your code base.)
Driver application:
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();
}
}
Aspect:
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());
}
}
As you can see, I also added source location info for demonstration purposes. I would not use it if you ask me, I just wanted to show you it exists.
Console log:
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
Update: If you switch to native AspectJ syntax - which I think is much more readable and elegant anyway for several reasons, eg because you can use imported classes in your pointcuts without fully qualifying package names - you can use thisEnclosingJoinPointStaticPart
for call()
pointcuts like this:
Modified aspect:
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());
}
}
New console log:
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
Update after OP significantly changed his question:
What you want is not possible. The reason can be seen in your own log output at the bottom of the question:
count
in your case - the chain of non-terminal functions before that one is set into motion. So even if you explicitly implement functional interfaces in classes instead of using lambdas, this is true. But then at least you could infer from the class name in your log what is happening:
Modified driver application:
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");
}
}
}
Modified aspect:
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);
}
}
Modified console log:
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
It does not get any better than this. If you want to have log output you actually understand, you need to refactor in the way I did. As I said: Only after count()
has been called, all the functions wired before that will be executed.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.