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 基础知识。
我有一个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 *)
.
有关详细信息,请参阅
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 基础知识。