春季领域

时间:2019-08-02 10:22:47

标签: java spring aspectj

我有一个要向InfluxDB报告的bean。表INFLUX_DB_SERVER中的数据库已注册InfluxDB。如果看一下代码,您会发现方法reportMemory做了大量的工作,它构造了一个Measurement并调用了reportAll,在没有InfluxDB的情况下,所有这些工作都是无用的。

因此,如果没有InfluxDB,则想法是跳过该工作。由于public-void-methods不返回值,因此对周围的应用程序没有影响。

我能做的是编写一个方法isWorkPossible,并在每次调用时都调用该方法。可能在KISS之后,但违反了DRY。因此,我喜欢使用AOP将其存档。

但是,如果没有注册InfluxDB,我想跳过所有公共无效方法的执行。

/**
 * 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 {
    }
}

我希望System.out.println在实例化bean时运行,但不会实例化。

知道为什么吗?

1 个答案:

答案 0 :(得分:1)

JB Nizet 已经说过,@annotation(my.package.MyAnnotation)旨在捕获方法而非字段上的注释,这解释了为什么您对在那里发生的事情的期望是错误的。

如果要通过AOP查找类是否具有带特定注释的成员,则需要使用特殊的切入点,例如hasfield(@MyAnnotation * *)。但是该切入点在Spring AOP中不可用,您需要switch to AspectJ。如果您想通过get(@MyAnnotation MyType *)set(@MyAnnotation MyType *)截取对此类字段的读/写访问,也是如此。

有关更多详细信息,请参见my other answer here

AspectJ还提供特殊的切入点

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

您可以使用它们来在适当时机执行方面建议。在您的示例中,如果很清楚所有目标类都具有其中之一,那么您也可以更轻松地使用@PostConstruct方法。

我的回答很笼统,因为您没有详细解释您想做什么。因此,随时提出后续问题。


更新:我检查了您的最新问题更新。我不明白,对于一个非常简单的问题,这是一个非常人为的解决方案,也不是AOP可以解决的一个好案例。尽管我非常喜欢AOP,但我仍然看不到这种情况是如何横切关注的:

  • 它似乎只影响单个类InfluxDBReporter
  • 您使用的注解仅用于告诉方面要做什么。
  • 使情况更糟的是,将注释放在私有字段上,但是希望外部类(在这种情况下为方面)对此做出反应。尽管AspectJ在技术上可以做到这一点,但它的设计不佳,因为您会将私人信息泄漏到外部。
  • 通过从示例类中跳过公共方法,您不会保存任何昂贵的与DB相关的操作,因为对空的KeySet进行迭代意味着什么都不会发生,因此也就不会有任何与DB相关的错误。真正发生的唯一事情是构建器调用。它们应该便宜。

即使假设您还有许多应该跳过的公共方法,但如果您确实愿意使用这种方法,我实际上会设计这样的AOP解决方案:

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

确切的解决方案取决于您是否只有这一类或具有相似要求的多个类(如果您只有public void方法或非无效方法)。

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

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