Hot questions for Using AspectJ in lambda

Question:

We have a custom annotation:

@Target(value = {ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface AsyncWithTimeout {
   long timeout();
}

And a custom @aspect which intercepts the methods annotated with @AsyncWithTimeout as follows, working fine (note the task definition, without lambda)

@Aspect
public class AsyncWithTimeoutInterceptor {

   @Around(value = "@within(com.foo.AsyncWithTimeout) || " + "@annotation(com.foo.AsyncWithTimeout)")
   public Object asyncWithTimeout(ProceedingJoinPoint point) {  
     long timeout = ((MethodSignature) point.getSignature()).getMethod().getAnnotation(AsyncWithTimeout.class).timeout()

     // Task without lambda -> OK
     Callable<Object> task = new Callable<Object>() {
        public Object call() {
           try {
              // Do some stuff with the timeout...
           } catch (Throwable throwable) {
              // Do some stuff...
           }
        }
     };

     // Do some stuff ...
   }
}

However, if we use a lambda instead to initialize task, an Exception is thrown:

@Aspect
public class AsyncWithTimeoutInterceptor {

   @Around(value = "@within(com.foo.AsyncWithTimeout) || " + "@annotation(com.foo.AsyncWithTimeout)")
   public Object asyncWithTimeout(ProceedingJoinPoint point) {  
     long timeout = ((MethodSignature) point.getSignature()).getMethod().getAnnotation(AsyncWithTimeout.class).timeout()

     // Task with lambda -> Error
     Callable<Object> task = () -> {
        try {
           // Do some stuff with the timeout...
        } catch (Throwable throwable) {
           // Do some stuff...
        }
     };

     // Do some stuff ...
   }
}

Exception detail:

java.lang.IllegalStateException: there is no classname for invokedynamic
        at org.aspectj.apache.bcel.generic.InvokeDynamic.getClassName(InvokeDynamic.java:126)
        at org.aspectj.weaver.bcel.BcelAccessForInlineMunger.openAroundAdvice(BcelAccessForInlineMunger.java:141)
        at org.aspectj.weaver.bcel.BcelAccessForInlineMunger.munge(BcelAccessForInlineMunger.java:80)
        at org.aspectj.weaver.bcel.BcelClassWeaver.weave(BcelClassWeaver.java:441)
        at org.aspectj.weaver.bcel.BcelClassWeaver.weave(BcelClassWeaver.java:101)
        at org.aspectj.weaver.bcel.BcelWeaver.weave(BcelWeaver.java:1689)
        at org.aspectj.weaver.bcel.BcelWeaver.weaveWithoutDump(BcelWeaver.java:1633)
        at org.aspectj.weaver.bcel.BcelWeaver.weaveAndNotify(BcelWeaver.java:1398)
        at org.aspectj.weaver.bcel.BcelWeaver.weave(BcelWeaver.java:1155)
        at org.aspectj.weaver.tools.WeavingAdaptor.getWovenBytes(WeavingAdaptor.java:527)
        at org.aspectj.weaver.tools.WeavingAdaptor.weaveClass(WeavingAdaptor.java:363)
        at org.aspectj.weaver.loadtime.Aj.preProcess(Aj.java:121)
        at org.aspectj.weaver.loadtime.ClassPreProcessorAgentAdapter.transform(ClassPreProcessorAgentAdapter.java:54)
        at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
        at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428)
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:763)

EDIT: Actually aspectjweaver version is 1.8.0

Versions used: jdk 1.8.0_124, aspectjweaver 1̶.̶9̶.̶1̶ 1.8.0

File aop.xml in my META-INF:

<aspectj>
   <aspects>
      <aspect name="com.foo.AsyncWithTimeoutInterceptor"/>
   </aspects>

   <weaver>
      <include within="com.foo.*"/>
   </weaver>
</aspectj>

Should I be able to use lambdas in aspects or there is some kind of limitation/bug? Any help would be appreciate.

Thanks in advance.


Answer:

Well, I made a mistake in my question and the aspectjweaver version used was actually 1.8.0 instead of 1.9.1.

EDIT: As @kriegaex points in a comment, the bug is already solved in the 1.8.13 aspectjweaver version (not necessary upgrading to 1.9.1) Thanks for pointing out.

I've just checked that the error is solved in version 1.8.13 and later, so lambdas can be used in aspects without problems.

Question:

Is it possible to have poitcut for scala lambdas? If I'm not mistaken scala lambdas now compiled in the same way as java lambdas, so I think my question is also applicable for java lambdas, but I'm not sure.

Here is the code. Basically I want to advice Runnable instances. And it works perfectly if I use classes or anonymous class, but it doesn't work if I use lambdas.

  @Around("execution(* (com.test..* && java.lang.Runnable+).run())")
  def runnableAspect(pjp: ProceedingJoinPoint): Any = {
    println("Runnable caught")
    pjp.proceed()
  }

And here is the test code:

package com.test

class Greet {

  def hello(): Unit = {
    println("-----start--------")
    run(new Runnable {
      override def run(): Unit = println("anonymous class")
    })
    println("------------------")
    run(() => println("lambda"))
    println("-----end--------")

  }

  private def run(r: Runnable) = r.run()

}

Output is:

-----start--------
Runnable caught
anonymous class
------------------
lambda
-----end--------

Is it possible at all? And if it is, what am I doing wrong?


Answer:

Bases on this answer.

For Java

@Around("execution(void com.test..lambda*(..)) || execution(* (com.test..* && java.lang.Runnable+).run())")

For Scala

@Around("execution(void com.test..$anonfun*(..)) || execution(* (com.test..* && java.lang.Runnable+).run())")

Question:

I am trying to use aspectJ to do AOP programming. But AJC compiler throw errors below, javac compiler works perfectly.

Error:(19, 0) ajc: The method getKey() is undefined for the type Object
Error:(19, 0) ajc: The method getValue() is undefined for the type Object
Error:(22, 0) ajc: Cannot refer to the static enum field ApplicationType.transport within an initializer
Error:(25, 0) ajc: Cannot refer to the static enum field ApplicationType.exposure within an initializer
Error:(28, 0) ajc: Cannot refer to the static enum field ApplicationType.manufacture within an initializer
Error:(31, 0) ajc: Cannot refer to the static enum field ApplicationType.factoryhub within an initializer

My source file is Maps.java ApplicationType.java --

public class Maps {
    public static <K, V> Map<K, V> toMap(Map.Entry<K, V>... entries) {
        return Collections.unmodifiableMap(
          Stream
            .of(entries)
             // compile error The method getKey() is undefined for the type Object
            .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())));
    }
}
ApplicationType.java 
public enum ApplicationType {

    transport(0),
    exposure(1),
    manufacture(2),
    factoryhub(3);

    private int code;

    ApplicationType(int code) {
        this.code = code;
    }

    ApplicationType(String application) {
        for (ApplicationType value : values()) {
            if (value.name().equals(application)) {
                switch (value) {
//ajc: Cannot refer to the static enum field ApplicationType.transport within an initializer
                    case transport:
                        setCode(0);
                        break;
                    case exposure:
                        setCode(1);
                        break;
                    case manufacture:
                        setCode(2);
                        break;
                    case factoryhub:
                        setCode(3);
                        break;
                    default:
                        break;
                }
            }
        }
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }
}

Answer:

Fix for

Error:(19, 0) ajc: The method getKey() is undefined for the type Object
Error:(19, 0) ajc: The method getValue() is undefined for the type Object

Reason for above errors is aspectj unable to discover the type of .toMap result. Lambda expressions are evaluated lazily whenever a collect operation is performed. To fix the error, see the variation of the code below. Store the results of .toMap operation explicitly and then user Collections.unmodifiableMap

public static <K, V> Map<K, V> toMap(final Map.Entry<K, V>... entries) {
    Map<K, V> map = Stream.of(entries).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    return Collections.unmodifiableMap(map);
}

Fix for:

Error:(22, 0) ajc: Cannot refer to the static enum field ApplicationType.transport within an initializer

Overloaded constructor Application(String name) for enum looks incorrect. You can simplify your enum code as shown below to get rid of error.

public enum ApplicationType {
    transport(0),
    exposure(1),
    manufacture(2),
    factoryhub(3);

    private int code;

    ApplicationType(int code) {
        this.code = code;
    }

    public static ApplicationType parse(String name) {
        return ApplicationType.valueOf(name.toLowerCase());
    }

    public int getCode() {
        return code;
    }
}

I have added a parse method in case you want the enum back based on name.

Hope this helps.

Question:

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 mapor 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]


What I have tried so far:

  • check the information in the JoinPoint. It contains the signature of the method that was created by the lambda expression. The name of the actual method is different (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.
  • try to find a more specific pointcut expression that only matches one of the calls. Again, the problem is that there is no way to know the name of the generated method for the map or filter lambda.
  • looking a the stack trace while 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:
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)
  • add a second aspect to the Stream's methods, printing out which Stream method is called with which parameter (that would be in case of 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]

Answer:

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, e.g. 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:

  • The stream method calls are long finished before the mapped functions are executed. Do not let the way the source code looks fool you.
  • This is because Java streams are lazy. Only when a terminal function is called - count in your case - the chain of non-terminal functions before that one is set into motion.
  • What I said above does not get any less complicated by the fact that there are parallel streams, too. There execution order is not necessarily linear anyway.

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.

Question:

I am trying typecast a lambda argument to a class type, however I am always getting classcast exception. Here is the use case I am trying to achieve.

  1. class A has a method public void foo(Supplier<?> msg)
  2. I put an Apsect on the foo() method to capture the argument(In this case actual value of msg)
  3. class B is invoking foo() method using an instance of class A using lambda expression
class B{
  public void bar(){
     A a=new A()
     a.foo(()->{ new MyCustomObject()});
  }
}

At runtime in my AOP class I am always getting argurment type of foo() as B$$lambda$0/13qdwqd

Question How do i get the actual class type of method Supplier argument(In this case MyCustomObject?

Aspect code

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]);
 }

}

Thanks in advance!


Answer:

Why are you making a simple thing complicated? User JB Nizet already told you:

The only way to get the type of the object returned by the supplier is to call the supplier.

Your own code does that in a very contrived way (after I fixed it to make it compile because it is buggy), using reflection. Just use AspectJ argument parameter binding via args() and make it type-safe. Also use @Before instead of @Around if you just want to log the Supplier's return value and not otherwise influence method execution. This way you can avoid calling ProceedingJoinPoint.proceed(), something necessary but completely missing in your "solution" sample code.

How about this little 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());
  }
}

The console log when running B.main(..):

execution(void de.scrum_master.app.A.foo(Supplier)) -> de.scrum_master.app.MyCustomClass@66a29884

This is the same as your aspect is trying to do, just more cleanly. I think it definitely is more readable, too.

Caveat: Please think twice before calling get() on the supplier if the supplier has a side effect or is expensive to calculate. I know that pure functions (i.e. code implementing functional interfaces in Java speak) should never have any side effects, but if coded in a bad style they easily can. So be careful.


Update: Talking about the caveat with the side effect, let me show you something. Just extend the application code a little bit so as to actually evaluate the supplier and (optionally) return its result:

package de.scrum_master.app;

import java.util.function.Supplier;

public class A {
  public Object foo(Supplier<?> msg) {
    return msg.get();
  }
}

And now also let us extend the aspect to actually trigger logging whenever a supplier's get() method is called:

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);
  }
}

Now the console log will be:

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

Can you see how Supplier.get() is called twice, returning two different MyCustomClass objects, namely MyCustomClass@66a29884 and MyCustomClass@4769b07b? This is because both the application and the first aspect advice call get(). The latter does not really log the same object as the one created by the application, so even without further side effects you are logging the wrong thing and executing a supplier method twice instead of just once.

So let us clean this up by not calling get() anymore from the first advice method:

  @Before("execution(* *(*)) && args(supplier)")
  public void methodCallWithSupplierArgument(JoinPoint thisJoinPoint, Supplier<?> supplier) throws Exception {
    System.out.println(thisJoinPoint + " -> " + supplier);  // no 'get()' call anymore
  }

Now the log becomes clean:

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

Another advantage is that now the get() result gets logged whenever the method is really called (could be done synchronously, asynchronously, multiple times or never) and not when the aspect executes it redundantly.

P.S.: If you are wondering why I am so meticulous about not executing get() just for logging purposes, just imagine that the lambda opens a database connection, creates a 4 GB file, downloads a 4K video with duration 90 minutes or whatever. It would be done twice, just so as to let you log it.