Spring 字段的方面

Spring Aspect for field

我有一个bean要报告给InfluxDB。数据库在 table INFLUX_DB_SERVER 中注册了 InfluxDB。如果你看代码你会发现方法 reportMemory 做了很多工作,它构造了一个 Measurement 并调用 reportAll,当没有 InfluxDB 时所有这些工作都是无用的。

所以如果没有 InfluxDB,我们的想法是跳过这项工作。由于 public-void-methods 没有 return 值,因此它对周围的应用程序没有影响。

我能做的是编写一个方法 isWorkPossible 并在每次调用时调用该方法。这可能遵循 KISS,但违反了 DRY。所以我喜欢使用 AOP 来存档。

但如果没有注册 InfluxDB,我喜欢跳过所有 public void 方法的执行。

/**
 * The reporter to notify {@link InfluxDB influxDBs} for changes.
 */
@Named
public class InfluxDBReporter {
    /**
     * Logger for reporting. For security reasons neither the username nor the
     * password should be logged above {@link Level#FINER}.
     */
    private static final Logger LOG = Logger.getLogger(InfluxDBReporter.class.getCanonicalName());

    /**
     * The entitymanager to use, never <code>null</code>.
     */
    @PersistenceContext
    private final EntityManager entityManager = null;

    /**
     * The registred {@link InfluxDBServer} in key and the URL in value.
     */
    @SkipPublicVoidMethodsIfEmpty
    private final Map<InfluxDB, URL> dbs = new LinkedHashMap<>();

    /**
     * Initializes the connections.
     */
    @PostConstruct
    private void connect() {
        for (InfluxDBServer db : FROM(囗InfluxDBServer.class).all(entityManager)) {
            try {
                URL dbUrl = new URL(db.getUrl());
                InfluxDB idb = InfluxDBFactory.connect(db.getUrl(), db.getUsername(), db.getPassword());
                idb.setDatabase(db.getDatabaseName());
                dbs.put(idb, dbUrl);
            } catch (MalformedURLException e) {
                LOG.log(Level.SEVERE, db.getUrl(), e);
            }
        }
    }

    /**
     * Closes all connections to all {@link InfluxDB}.
     */
    @PreDestroy
    private void disconnect() {
        for (InfluxDB influxDB : dbs.keySet()) {
            try {
                influxDB.close();
            } catch (Exception e) {
                // Fault barrier
                LOG.log(Level.WARNING, "InfluxDBServer URL: " + dbs.get(idb), e);
            }
        }
    }

    /**
     * Report memory statistics.
     * 
     * @param availableProcessors Amount of available processors, never negative.
     * @param totalMemory         The total memory, never negative.
     * @param maxMemory           The max memory, never negative.
     * @param freeMemory          The free memory, never negative.
     */
    public void reportMemory(int availableProcessors, long totalMemory, long maxMemory, long freeMemory) {
        reportAll(Point.measurement("jvm").addField("totalMemory", totalMemory).addField("maxMemory", maxMemory)
                .addField("freeMemory", freeMemory));
    }

    /**
     * Report a point to all connected {@link InfluxDBServer}.
     * 
     * @param builder The point to report.
     */
    private void reportAll(Builder builder) {
        Point infoPoint = builder.time(System.currentTimeMillis(), TimeUnit.MILLISECONDS).build();
        for (InfluxDB idb : dbs.keySet()) {
            new Thread(() -> {
                try {
                    idb.write(infoPoint);
                } catch (Exception e) {
                    // Fault barrier
                    LOG.log(Level.WARNING, "InfluxDBServer URL: " + dbs.get(idb), e);
                    throw e;
                }
            }).start();
        }
    }
}

这是我的方面:

@Aspect
public class MethodAnnotations {
    @Pointcut("@annotation(xxx.MethodAnnotations.SkipPublicVoidMethodsIfEmpty)")
    private void anyOldTransfer(JoinPoint jp) {
        System.out.println(jp); <----- never executed.
    }

    public @interface SkipPublicVoidMethodsIfEmpty {
    }
}

我希望实例化 bean 时 System.out.println 到 运行 但它没有。

知道为什么吗?

正如 JB Nizet 所说,@annotation(my.package.MyAnnotation) 旨在捕获方法上的注释,而不是字段上的注释,这解释了为什么您对任何事情发生的期望都是错误的.

如果你想通过 AOP 找出 class 是否有一个带有特定注释的成员,你需要使用像 hasfield(@MyAnnotation * *) 这样的特殊切入点。但是那个切入点在Spring AOP 中不可用,你需要switch to AspectJ。如果您想通过 get(@MyAnnotation MyType *)set(@MyAnnotation MyType *).

拦截对此类字段的 read/write 访问,也是如此

有关详细信息,请参阅

AspectJ 还提供了特殊的切入点

  • 在 class 加载后拦截 class 的静态初始化 -> staticinitialization()
  • 拦截构造函数执行 -> MyType.new()

您可以在适当的时候使用它们来执行您的方面建议。在您的示例中,如果很明显所有目标 class 都有其中之一,您还可以更轻松地挂接到 @PostConstruct 方法。

我的回答很笼统,因为你没有详细解释你到底想做什么。所以请随时提出后续问题。


更新: 我查看了你最新的问题更新。我不明白,这是针对一个非常简单的问题的非常人为的解决方案,也不是 AOP 解决的好案例。尽管我很喜欢 AOP,但我看不出这种情况是一个横切关注点:

  • 好像只影响一个class,InfluxDBReporter.
  • 您使用的注释的唯一目的是告诉方面要做什么。
  • 更糟糕的是,您将注释放在私有字段上,但期望外部 class(在本例中为方面)对其作出反应。虽然这在技术上使用 AspectJ 是可行的,但它是糟糕的设计,因为您将私人信息泄漏到外部。
  • 通过跳过示例 class 中的 public 方法,您不会保存任何昂贵的与数据库相关的操作,因为遍历空 KeySet 意味着什么都不会发生,所以也不会有任何与数据库相关的错误。这里唯一真正发生的是构建器调用。它们应该很便宜。

即使假设您有更多 public 方法应该被跳过,如果您确实想坚持使用该方法,我实际上会设计这样的 AOP 解决方案:

  • 将方法 public boolean isConnectedToDB() { return !dbs.isEmpty(); } 添加到您的应用程序 class。
  • 在您方面,使用 @Around 建议并从那里调用布尔方法,只有在有任何连接时才调用 joinPoint.proceed()。否则不要继续,而是什么也不做(对于 void 方法)或 return 像 null 这样的虚拟结果(对于非 void 方法)。

确切的解决方案取决于你是否只有这个 class 或具有类似要求的多个,如果你只有 public void 方法或非空方法。

此外,您提到了 INFLUX_DB_SERVER,但我不知道这是什么,因为我在您的代码中的任何地方都看不到它。

最后但同样重要的是:我刚刚注意到您希望在 @Pointcut 注释的方法中发生某些事情。抱歉,即使切入点没有错,那里也会发生一些事情,因为切入点定义只是用于实际的建议方法,例如 @Before@After@Around。您想要执行的操作进入通知,而不是进入切入点。我建议您在尝试设计基于 AOP 的解决方案之前先学习 AOP 基础知识。