0

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

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

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

4

1 回答 1

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());
于 2020-05-13T11:03:40.947 回答