@Aspect - getSignature() 为空

@Aspect - getSignature() is null

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

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

@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。有人能告诉我为什么会这样以及如何解决吗?

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

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

现在我的猜测是你的目标 class 有超过你感兴趣的两种方法,并且在执行其中一种方法时你的地图访问 returns 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 中没有 "functions",只有方法。我也没有在不需要的地方一直使用 this 并删除了一些重复的代码。他们的方式是我用 0 动态初始化一个新的地图条目,如果它不存在,但如果你不知道如何 read/write lambda 表达式,你可能会觉得很奇怪。您可以通过

轻松地使用更 class 的编程风格
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: 还有另一种迭代地图的方法:使用它的条目(key/value 对):

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