Search code examples
javaaspectpointcut

@Aspect - getSignature() is null


I created simple aspect to count how many times specific method is executed. I have to tell that I'm doing it first time so probably it's not really pretty.

First, I created something like that:

@Aspect
@Component
public class ItemAspect {
    private Map<String, Integer> functionsCalls = new HashMap<>();

    public ItemAspect(){
        this.functionsCalls.put("ItemApiController.addItem()", 0);
        this.functionsCalls.put("ItemApiController.getItems()", 0);
    }

    @Pointcut("execution(* io.swagger.api.ItemApiController.getItems(..))")
    private void itemApiControllerEx(){ }

    @After("itemApiControllerEx()")
    public void doAfterItemApiControllerEx (JoinPoint joinPoint) {
        this.functionsCalls.put(joinPoint.getSignature().toString(), this.functionsCalls.get(joinPoint.getSignature().toString())+1);
    }

    public Map<String, Integer> getFunctionsCalls () {
        return functionsCalls; }
}

and it counted how many times "getItems()" was executed correctly. (Yes, for "addItem()" it work too.) But I want to count both methods, so I converted code to:

@Aspect
@Component
public class ItemAspect {
    private Map<String, Integer> functionsCalls = new HashMap<>();

    public ItemAspect(){
        this.functionsCalls.put("ItemApiController.addItem()", 0);
        this.functionsCalls.put("ItemApiController.getItems()", 0);
    }

    @Pointcut("execution(* io.swagger.api.ItemApiController.*(..))")
    private void itemApiControllerEx(){ }

    @After("itemApiControllerEx()")
    public void doAfterItemApiControllerEx (JoinPoint joinPoint) {
        this.functionsCalls.put(joinPoint.getSignature().toString(), this.functionsCalls.get(joinPoint.getSignature().toString())+1);
    }

    public Map<String, Integer> getFunctionsCalls () {
        return functionsCalls; }
}

and now I'm getting NullPointerException when I try to get signature. Can someone tell me why this is happening and how to fix it?


Solution

  • Because you did not share your application code - please learn what an MCVE is - I cannot just run it or even take a look at your application class, but from what I see from my cursory glance at your aspect code is that you are trying to access a map value like this:

    functionsCalls.get(joinPoint.getSignature().toString())
    

    Now my guess is that your target class has more than the two methods you are interested in and upon execution of one of them of course your map access returns null for the non-existing key's value. So the NullPointerException would not come from trying to get the joinpoint signature but from trying to increment a null value like this:

    myMap.get(nonExistentValue) + 1  // null + 1 => NullPointerException
    

    You should program a bit more defensively.


    Update: Here is an MCVE in pure AspectJ, but the aspect syntax in Spring AOP is the same.

    package io.swagger.api;
    
    public class ItemApiController {
      public void addItem(Object item) {}
    
      public Object getItems() {
        return "dummy";
      }
    
      public void doSomethingElse() {}
    }
    
    package io.swagger.api;
    
    import java.util.Map;
    
    public class Application {
      public static void main(String[] args) {
        ItemApiController controller = new ItemApiController();
        controller.addItem("A");
        controller.getItems();
        controller.addItem("B");
        controller.doSomethingElse();
        controller.addItem("C");
        controller.getItems();
        controller.doSomethingElse();
        controller.addItem("C");
    
        Map<String, Integer> statistics = ItemAspect.getStatistics();
        for (String signature : statistics.keySet())
          System.out.printf("%3d  |  %s%n", statistics.get(signature), signature);
      }
    }
    
    package io.swagger.api;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    
    @Aspect
    public class ItemAspect {
      private static Map<String, Integer> methodCalls = new HashMap<>();
    
      @Pointcut("execution(* io.swagger.api.ItemApiController.*(..))")
      private void itemApiControllerEx() {}
    
      @After("itemApiControllerEx()")
      public void doAfterItemApiControllerEx(JoinPoint joinPoint) {
        String signature = joinPoint.getSignature().toString();
        methodCalls.computeIfAbsent(signature, newSignature -> 0);
        methodCalls.put(signature, methodCalls.get(signature) + 1);
      }
    
      public static Map<String, Integer> getStatistics() {
        return methodCalls;
      }
    }
    

    You may notice that I renamed a few things, e.g. in Java there are no "functions" but methods. I also did not use this all the time where it was not necessary and removed some duplicate code. They way I dynamically initialise a new map entry with 0 if it does not exist yet might seem strange to you if you do not know how to read/write lambda expressions. You can just as easily use a more classical programming style with

    if (!methodCalls.containsKey(signature))
      methodCalls.put(signature, 0);
    

    Besides, I kept your idea of using signature.toString() as a map key, but you could also use the Signature objects themselves or maybe cast them to MethodSignature first, then later you would have the option which part you want to extract for printing. But that is just icing on the cake. Strings take less memory than the full signature objects, so that is fine too.

    If you run the sample Application, you will get this console output:

      2  |  void io.swagger.api.ItemApiController.doSomethingElse()
      2  |  Object io.swagger.api.ItemApiController.getItems()
      4  |  void io.swagger.api.ItemApiController.addItem(Object)
    

    Update 2: There is also another way to iterate over a map: using its entries (key/value pairs):

        for (Entry<String, Integer> entry : statistics.entrySet())
          System.out.printf("%3d  |  %s%n", entry.getValue(), entry.getKey());