【问题标题】:@Aspect - getSignature() is null@Aspect - getSignature() 为空
【发布时间】:2020-08-29 10:35:29
【问题描述】:

我创建了一个简单的方面来计算特定方法执行了多少次。我不得不说我是第一次这样做,所以它可能不是很漂亮。

首先,我创建了类似的东西:

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

它会计算正确执行“getItems()”的次数。 (是的,对于“addItem()”它也可以工作。) 但我想同时计算这两种方法,所以我将代码转换为:

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

现在,当我尝试获取签名时,我得到了 NullPointerException。有人能告诉我为什么会发生这种情况以及如何解决吗?

【问题讨论】:

    标签: java aspect pointcut


    【解决方案1】:

    因为您没有共享您的应用程序代码 - 请了解 MCVE 是什么 - 我不能只运行它甚至查看您的应用程序类,但从我粗略查看您的方面代码中看到的是您正在尝试访问这样的地图值:

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

    现在我的猜测是,您的目标类拥有的方法不止您感兴趣的两种方法,在执行其中一种方法时,您的地图访问当然会为不存在的键值返回 null。因此,NullPointerException 不会来自尝试获取连接点签名,而是来自尝试增加 null 值,如下所示:

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

    你应该更加防御性地编程。


    更新:这是纯AspectJ中的MCVE,但Spring AOP中的切面语法是一样的。

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

    您可能会注意到我重命名了一些东西,例如在 Java 中没有“函数”,只有方法。我也没有在不必要的时候一直使用this,并删除了一些重复的代码。如果你不知道如何读/写 lambda 表达式,我会用 0 动态初始化一个新的映射条目,如果它不存在但对你来说可能很奇怪。您可以通过

    轻松使用更经典的编程风格
    if (!methodCalls.containsKey(signature))
      methodCalls.put(signature, 0);
    

    此外,我保留了您使用 signature.toString() 作为映射键的想法,但您也可以使用 Signature 对象本身,或者先将它们转换为 MethodSignature,然后您可以选择哪一部分想提取打印。但这只是锦上添花。字符串比完整的签名对象占用更少的内存,所以这也很好。

    如果你运行示例Application,你会得到这个控制台输出:

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

    更新 2:还有另一种迭代地图的方法:使用其条目(键/值对):

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

    【讨论】:

    • 是的,谢谢,我在一分钟前发现了这一点,然后回到这里写了答案,当我看到你的消息时我找到了解决办法。感谢您抽出宝贵时间和 MCVE,这将使将来的提问变得更容易。
    • 此外,您的签名看起来不像ItemApiController.addItem(),而是像void io.swagger.api.ItemApiController.addItem(Object),所以这也不起作用。我会在几分钟内为您提供一个干净的解决方案的 MCVE。
    • 我添加了 MCVE 是为了让您了解更多关于 Java 编程的知识,因为您似乎是一个新手。我希望它有所帮助。
    猜你喜欢
    • 1970-01-01
    • 2012-03-26
    • 2020-12-17
    • 1970-01-01
    • 2016-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多