-2

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!

Naman
  • 27,789
  • 26
  • 218
  • 353
Arajit
  • 133
  • 3
  • 15
  • 1
    Can you show the code that actually produces an exception? – luk2302 Jan 13 '20 at 18:56
  • 3
    you get what you ask for. `B$$lambda$0/13qdwqd` is the implementation of that `Supplier`, and no, you can't get the `MyCustomObject.class` without modifying your code in some way. – Eugene Jan 13 '20 at 19:20
  • @luk2302 I added the Aspect code from where I am getting error. – Arajit Jan 13 '20 at 19:34
  • @Eugene I can update my code. Any suggestion/documentation link on how to do it ? – Arajit Jan 13 '20 at 19:35
  • 1
    @Arajit what do you *actually* want to get? What is the concrete problem you're trying to solve? – JB Nizet Jan 13 '20 at 19:36
  • @JBNizet I have bunch of applications in multiple code bases where developers are writing Log statements. Log statements have been written using lambda expression `log.info({}->new MyCutomObject})`. My goal to capture the actual message being logged using Aspect and create an Inventory. – Arajit Jan 13 '20 at 19:44
  • The only way to get the type of the object returned by the supplier is to **call** the supplier. – JB Nizet Jan 13 '20 at 19:46
  • @JBNizet you mean supplier.get() ?? If so i dont think i can do that as its not an instance of java.util.function.Supplier – Arajit Jan 13 '20 at 19:55
  • 1
    of course it is. – Eugene Jan 13 '20 at 19:59
  • @Eugene I really did not get your last comment – Arajit Jan 13 '20 at 20:54
  • @Arajit you said: "i dont think i can do that as its not an instance of java.util.function.Supplier". Eugene replied with "of course it is". Your method takes a Supplier as argument. So of course its argument is a Supplier. – JB Nizet Jan 14 '20 at 07:22

2 Answers2

1

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.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Thanks for the details explanation.Regarding code snippet from my original post, i did not know that code has to be compilable. apology for that. Regarding your solution, it still did not solve my problem. I am still not able to capture the supplier argument with aspect `@Before("execution(* org.apache.logging.log4j.Logger.*(*)) && args(supplier)")`. My aspect is not firing at all. If I remove the supplier from pointcut expression then my aspect executes. With this when i printout the arguments i am still getting argument as classs$$lambda$@132e434. – Arajit Jan 15 '20 at 16:29
  • Note that `org.apache.logging.log4j.Logger` is an interface from log4j2 library. As I mentioned earlier the problem I am trying to solve is to capture Object that has been passed as a java8 Supplier like `log.info(()->new MyCustomClass())` – Arajit Jan 15 '20 at 16:33
  • Why are you surprised that your code should compile? Nobody can really help you if you post broken or pseudo code. Please be advised to learn what an [MCVE](https://stackoverflow.com/help/mcve) is and then present me with one by editing your question or by pushing a small project on GitHub, then notifying me via comment. With the little code snippets presented in your previous comments it is impossible for me to reproduce and analyse your problem. – kriegaex Jan 16 '20 at 02:01
  • Besides, my answer precisely does what you asked for. You must have made a mistake adapting it to your needs, such as adding the supplier to the pointcut but forgetting to also add it to the advice method signature as a parameter. Whatever it is, I cannot see it and hate to speculate. – kriegaex Jan 16 '20 at 02:04
  • How about importing the right class? Instead of import `java.util.function.Supplier` Log4J uses its own `org.apache.logging.log4j.util.Supplier`. If you replace the import it will work. – kriegaex Jan 16 '20 at 08:52
  • BTW, if you use load-time weaving (which is indeed necessary in order to dynamically weave into 3rd party classes like Log4J) and aspects in annotation syntax (not native AspectJ syntax) you don't need to use AspectJ Maven Compiler because the weaving agent _aspectjweaver_ will take care of finishing the Java class into an aspect before weaving. – kriegaex Jan 16 '20 at 08:54
  • I just created a [pull request](https://github.com/arajitsamanta/aspect-aop/pull/1) for your convenience. – kriegaex Jan 16 '20 at 10:12
0

Thanks you everyone for your comments. I was able to solve it by invoking get() method on the lambda. Sample code give below


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

 }

}
Arajit
  • 133
  • 3
  • 15
  • Firstly, your class is missing an `@Aspect` annotation. Secondly, your `@Around` advice is not returning anything, so it would never compile like this. So now after you created an instance of the supplier's parameter type, what are you going to do with it? You definitely cannot return it because the method should return a supplier, not an instance of the the object type to be supplied. Please explain what you think you gain by doing this. The code does not make any sense to me and it seems you don't really understand what lambdas are used for. – kriegaex Jan 14 '20 at 01:00
  • Furthermore, you have mismatching class names `MyCustomObj` and `MyCustomClass`. Then also your line `Object obj = method.invoke(obj, null);` neither makes sense nor compiles. Finally, how can you expect iterating over all methods of a class with the same set of parameters will work without exception handling. This is not a solution, it only creates more confusion. So please update both your question and your answer in order to be consistent and to explain what you are actually trying to do and how to achieve it. – kriegaex Jan 14 '20 at 01:26