【问题标题】:How to get pointcut context in aspect association?如何在方面关联中获取切入点上下文?
【发布时间】:2021-09-12 10:24:30
【问题描述】:

所以我在 AspectJ 上做一些练习是为了好玩和练习,并且想要实现一个记忆方面,因为这是最常见的用例之一。

这包括使用这个注释

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Memoized {
    String value();
}

我想传递一个 String 代表 Duration 使用基于 ISO-8601 秒的表示,所以,类似于:

    @Memoized("PT1H30M")
    public int memoizedMethod() {
        return new Random().nextInt();
    }

因此,为此,我必须创建一个包含 Guava 缓存的方面,并使用使用方法签名作为键并缓存或从缓存中检索结果的环绕建议,我得出了这个结论:

public aspect Memoize pertarget(memoized(Memoized)) {
    private final Cache<String, Object> cache = CacheBuilder.newBuilder().expireAfterWrite(Duration.ofMinutes(1)).build();

    public pointcut memoized(Memoized memoized) : call(@Memoized * *.*()) && @annotation(memoized) ;

    Object around(Memoized memoized): memoized(memoized) {
        var key = thisJoinPoint.getSignature().toShortString();

        try {
            return cache.get(key, () -> proceed(memoized));
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }
}

但是,这并不能满足我的需求。如您所见,我在缓存创建中硬编码了 1 分钟的 Duration。我设法在切入点中捕获了@Memoized 内容,并可以在建议中使用它来执行Duration.parse(memoized.value()),但我需要在方面关联中使用此值,即pertarget(memoized())

我已经阅读了官方文档,但我仍然不确定切面关联中切入点的参数的真正含义,我只是将Memoized 放在那里,因为我在示例中看到了它,否则不会编译.

有什么方法可以捕获注释的 memoized 实例,然后像在建议中那样在缓存实例化中使用它?

【问题讨论】:

  • 创建partarget 方面实例而不是使用默认的单例实例化模型的基本原理是什么?如果这不是您的发明,请提供原始代码的链接,以便我更好地理解。
  • 如果我理解得很好(我是 aspectj 的新手),单例实例化模型将意味着对于任何建议的方法都只会有这个 aspect(以及缓存)的一个实例。我想要的是每个建议方法的一个实例,并在注释中指定缓存持续时间。事后看来,也许 pertarget 对此也无效?
  • pertarget 为每个目标对象实例创建一个实例,并且给定切入点与之匹配。如果您只是希望每个控制流都有一个实例,那么percflow 可能是更好的实例化模型,请参阅here。或者,您可以坚持单例方面并管理一组缓存,每个方法一个。
  • 您应该使用哪种实例化模型和方面实现取决于您需要的缓存策略。您是否需要缓存每个目标实例的方法结果,因为目标状态会影响方法结果?记忆方法是否更像纯函数,它们为同一组输入参数产生完全相同的输出?那部分并不是真正的 AspectJ 问题。
  • 纠正我自己:percflow 可能也不是您想要的,因为它会创建一个新的方面实例,而不是每个方法,而是每次执行该方法时。我认为您应该坚持使用单例方面并使其管理一组缓存,每个目标方法一个。

标签: java aop aspectj


【解决方案1】:

我认为您使用了错误的缓存键。您应该使用由方法参数的表示组成的缓存键,而不是方法名称。否则,无论使用哪个参数,对于每个方法调用,您总是会从缓存中获得完全相同的结果。

这里有一点MCVE,使用单例方面:

package de.scrum_master.app;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(METHOD)
public @interface Memoized {
  String value();
}
package de.scrum_master.app;

import org.aspectj.lang.Aspects;

import de.scrum_master.aspect.Memoize;

public class Application {
  public static void main(String[] args) {
    Application application;

    for (int i = 0; i < 3; i++) {
      application = new Application();
      System.out.println(application.capitalise("foo"));
      System.out.println(application.triple(11));
      System.out.println(application.capitalise("bar"));
      System.out.println(application.triple(22));
    }

    Aspects.aspectOf(Memoize.class).printCacheStats();
  }

  @Memoized("PT1M45S")
  public String capitalise(String string) {
    System.out.println("Capitalising " + string);
    return string.toUpperCase();
  }

  @Memoized("PT1H30M")
  public int triple(int i) {
    System.out.println("Tripling " + i);
    return 3 * i;
  }
}
package de.scrum_master.aspect;

import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;

import org.aspectj.lang.SoftException;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

import de.scrum_master.app.Memoized;

public aspect Memoize {
  private final Map<String, Cache<String, Object>> caches = new HashMap<>();

  public pointcut memoized(Memoized memoized) : execution(* *(..)) && @annotation(memoized);

  Object around(Memoized memoized): memoized(memoized) {
    String cacheID = thisJoinPoint.getSignature().toString();
    Cache<String, Object> cache = caches.get(cacheID);
    if (cache == null) {
      cache = CacheBuilder.newBuilder().recordStats().expireAfterWrite(Duration.parse(memoized.value())).build();
      caches.put(cacheID, cache);
    }
    System.out.println(thisJoinPoint + " -> " + cache);
    String args = Arrays.deepToString(thisJoinPoint.getArgs());
    try {
      return cache.get(cacheID + "#" + args, () -> proceed(memoized));
    } catch (ExecutionException e) {
      throw new SoftException(e);
    }
  }
  
  public void printCacheStats() {
    caches.entrySet().forEach(entry -> System.out.println(entry.getKey() + " -> " +entry.getValue().stats()));
  }
}

如您所见,我在方面添加了一个额外的方法,因此我们可以在控制台上看到一些统计信息。在运行驱动程序应用程序时,我们预计这两种方法中的每一种都有 2 次缓存未命中和 4 次命中。让我们找出控制台日志的内容:

execution(String de.scrum_master.app.Application.capitalise(String)) -> com.google.common.cache.LocalCache$LocalManualCache@4ee285c6
Capitalising foo
FOO
execution(int de.scrum_master.app.Application.triple(int)) -> com.google.common.cache.LocalCache$LocalManualCache@7dc5e7b4
Tripling 11
33
execution(String de.scrum_master.app.Application.capitalise(String)) -> com.google.common.cache.LocalCache$LocalManualCache@4ee285c6
Capitalising bar
BAR
execution(int de.scrum_master.app.Application.triple(int)) -> com.google.common.cache.LocalCache$LocalManualCache@7dc5e7b4
Tripling 22
66
execution(String de.scrum_master.app.Application.capitalise(String)) -> com.google.common.cache.LocalCache$LocalManualCache@4ee285c6
FOO
execution(int de.scrum_master.app.Application.triple(int)) -> com.google.common.cache.LocalCache$LocalManualCache@7dc5e7b4
33
execution(String de.scrum_master.app.Application.capitalise(String)) -> com.google.common.cache.LocalCache$LocalManualCache@4ee285c6
BAR
execution(int de.scrum_master.app.Application.triple(int)) -> com.google.common.cache.LocalCache$LocalManualCache@7dc5e7b4
66
execution(String de.scrum_master.app.Application.capitalise(String)) -> com.google.common.cache.LocalCache$LocalManualCache@4ee285c6
FOO
execution(int de.scrum_master.app.Application.triple(int)) -> com.google.common.cache.LocalCache$LocalManualCache@7dc5e7b4
33
execution(String de.scrum_master.app.Application.capitalise(String)) -> com.google.common.cache.LocalCache$LocalManualCache@4ee285c6
BAR
execution(int de.scrum_master.app.Application.triple(int)) -> com.google.common.cache.LocalCache$LocalManualCache@7dc5e7b4
66
String de.scrum_master.app.Application.capitalise(String) -> CacheStats{hitCount=4, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=19678400, evictionCount=0}
int de.scrum_master.app.Application.triple(int) -> CacheStats{hitCount=4, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=252000, evictionCount=0}

多多! ?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-08-31
    • 1970-01-01
    • 2014-03-23
    • 1970-01-01
    • 1970-01-01
    • 2014-05-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多