我正在尝试编写一个方面,如果系统属性具有某些特定值,它将拦截所有方法调用。但是,我不需要从建议控制流中拦截任何方法。
我正在使用!cflow(adviceexecution())
表达式来实现此目的,但是它似乎无法与if()
表达式结合使用。结果,由于无限递归,我得到了StackOverflowError
。
方面代码:
@Aspect
public class SomeAspect {
@Pointcut("execution(* *.*(..)) && if()")
public static boolean allMethodCalls() {
return PropertyReader.hasSpecificProperty();
}
@Pointcut("cflow(adviceexecution())")
public void aspectCalls() {
}
@Before("allMethodCalls() && !aspectCalls()")
public void logSomething() {
// logging behavior
}
}
PropertyReader代码:
public class PropertyReader {
public static boolean hasSpecificProperty() {
return System.getProperty("specificProperty") != null;
}
}
答案 0 :(得分:1)
adviceexecution()
不匹配,因为您的动态if()
切入点在建议执行之前之前被评估。毕竟,这就是if()
的全部内容:决定是否应该执行建议。
让我们说,情况是这样的:
package de.scrum_master.app;
public class PropertyReader {
public static boolean hasSpecificProperty() {
return System.getProperty("specificProperty") != null;
}
public void doSomething(String info) {
System.out.println("Doing something " + info);
hasSpecificProperty();
}
public static void main(String[] args) {
System.clearProperty("specificProperty");
new PropertyReader().doSomething("with inactive property");
System.setProperty("specificProperty", "true");
new PropertyReader().doSomething("with active property");
System.clearProperty("specificProperty");
}
}
现在,最简单的解决方案是将hasSpecificProperty()
逻辑引入方面本身,因为它是微不足道的。您可以定义局部静态方法,也可以将其内联到if()
切入点中:
package de.scrum_master.app;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SomeAspect {
@Pointcut("execution(* *(..)) && if()")
public static boolean allMethodCalls() {
return System.getProperty("specificProperty") != null;
}
@Pointcut("cflow(adviceexecution())")
public void aspectCalls() {}
@Before("allMethodCalls() && !aspectCalls()")
public void logSomething(JoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
PropertyReader.hasSpecificProperty();
}
}
这将产生以下控制台日志:
Doing something with inactive property
execution(void de.scrum_master.app.PropertyReader.doSomething(String))
Doing something with active property
execution(boolean de.scrum_master.app.PropertyReader.hasSpecificProperty())
如您所见,从应用程序甚至从方面的建议中调用hasSpecificProperty()
都没有问题,因为在一个有问题的地方它是内联的。
如果要避免将方法内联或复制到方面,则需要在方面内进行手动记账,恐怕:
package de.scrum_master.app;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SomeAspect {
private static ThreadLocal<Boolean> isInPointcut = new ThreadLocal<Boolean>() {
@Override protected Boolean initialValue() { return false; }
};
@Pointcut("execution(* *(..)) && if()")
public static boolean allMethodCalls() {
if (isInPointcut.get())
return false;
isInPointcut.set(true);
boolean result = PropertyReader.hasSpecificProperty();
isInPointcut.set(false);
return result;
}
@Pointcut("cflow(adviceexecution()) || within(SomeAspect)")
public void aspectCalls() {}
@Before("allMethodCalls() && !aspectCalls()")
public void logSomething(JoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
PropertyReader.hasSpecificProperty();
}
}
控制台日志是相同的。请注意,|| within(SomeAspect)
是必要的,以避免捕获匿名ThreadLocal
类。
更新:刚刚问了以下后续问题:
我不太明白为什么我们需要
ThreadLocal
而不是简单的boolean
标志。你能解释一下吗?
简短的答案是:为了使方面成为线程安全的。如果有多个线程同时读写静态成员isInPointcut
ThreadLocal
变量,为每个线程提供独立的标志实例,从而使线程可以并行进行。如果您不执行任何操作,则您的方面将中断,并读取其他线程设置的标志的错误值。我将向您展示。让我们如下更改演示应用程序:
package de.scrum_master.app;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class PropertyReader {
private static int callCounter = 0;
private static final Random RANDOM = new Random();
public static boolean hasSpecificProperty() {
synchronized (RANDOM) {
callCounter++;
}
try {
Thread.sleep(25);
} catch (InterruptedException e) {
e.printStackTrace();
}
return System.getProperty("specificProperty") != null;
}
public void doSomething(String info) {
System.out.println("Doing something " + info);
hasSpecificProperty();
}
public static int doStuff(final int numThreads, final boolean specificPropertyState) throws InterruptedException {
if (specificPropertyState)
System.setProperty("specificProperty", "true");
else
System.clearProperty("specificProperty");
List<Thread> threads = new ArrayList<>();
long startTime = System.currentTimeMillis();
callCounter = 0;
for (int i = 0; i < numThreads; i++) {
Thread newThread = new Thread(() -> {
new PropertyReader().doSomething("with active property");
});
threads.add(newThread);
newThread.start();
}
for (Thread thread : threads)
thread.join();
System.clearProperty("specificProperty");
System.out.println("Call counter = " + callCounter);
System.out.println("Duration = " + (System.currentTimeMillis() - startTime) + " ms");
return callCounter;
}
public static void main(String[] args) throws InterruptedException {
final int NUM_THREADS = 10;
int callCounterInactiveProperty = doStuff(NUM_THREADS, false);
int callCounterActiveProperty = doStuff(NUM_THREADS, true);
int callCounterDelta = callCounterActiveProperty - callCounterInactiveProperty;
if (callCounterDelta != 3 * NUM_THREADS)
throw new RuntimeException("Call counter delta should be " + 3 * NUM_THREADS + ", not " + callCounterDelta);
}
}
您可能需要一段时间来理解此代码。基本上,我会执行以下操作:
static int callCounter
,其目的是方法static boolean hasSpecificProperty()
可以使用它来计数调用它的频率。static final Random RANDOM
(也可以是任何其他对象类型)来同步callCounter
,因为即使我们将其{{1 }}而不是callCounter
,因为递增计数器时,我们总是必须创建一个新的Integer
实例,该实例不同于同步的实例。我尝试过,有时算错了。相信我,我尝试过。int
使Integer
变慢,从而引发并发问题。您自己说过,该方法的版本比您在问题中显示的方法复杂。hasSpecificProperty()
,该方法创建用户定义数量的线程,并根据用户调用Thread.sleep(25)
的方式设置或取消设置系统属性static int doStuff(final int numThreads, final boolean specificPropertyState)
并发运行它们。然后,该方法等待直到所有线程完成,然后打印持续时间并返回specificProperty
的当前值。如果方面正常工作,则对于相同的方法参数,此返回值应始终相同。doStuff(..)
现在两次调用callCounter
,首先使用无效的系统属性,然后使用有效的系统属性。两个变体之间应该有一个差异(增量),因为如果属性处于活动状态,则main(..)
会更频繁地执行,因为从方面建议doStuff(..)
内它会被调用,并且只有在系统处于执行状态时才会执行此建议属性由hasSpecificProperty()
切入点确定。现在,如果我们运行该程序,控制台日志会说(略):
logSomething(..)
调用计数器总是相差if()
,因为在活动的系统属性下,每个线程将截获三个方法执行,因此建议运行3次,并且每次也调用Doing something with active property
Doing something with active property
(...)
Doing something with active property
Doing something with active property
Call counter = 40
Duration = 151 ms
execution(void de.scrum_master.app.PropertyReader.lambda$0())
execution(void de.scrum_master.app.PropertyReader.lambda$0())
(...)
execution(void de.scrum_master.app.PropertyReader.lambda$0())
execution(void de.scrum_master.app.PropertyReader.lambda$0())
execution(void de.scrum_master.app.PropertyReader.doSomething(String))
execution(void de.scrum_master.app.PropertyReader.doSomething(String))
(...)
execution(void de.scrum_master.app.PropertyReader.doSomething(String))
execution(void de.scrum_master.app.PropertyReader.doSomething(String))
Doing something with active property
Doing something with active property
(...)
Doing something with active property
Doing something with active property
execution(boolean de.scrum_master.app.PropertyReader.hasSpecificProperty())
execution(boolean de.scrum_master.app.PropertyReader.hasSpecificProperty())
(...)
execution(boolean de.scrum_master.app.PropertyReader.hasSpecificProperty())
execution(boolean de.scrum_master.app.PropertyReader.hasSpecificProperty())
Call counter = 70
Duration = 180 ms
。
现在,如果我们“简化”(从而中断)像这样的方面:
3 * NUM_THREADS
控制台日志更改为:
hasSpecificProperty()
哦!计数以意外的方式不同,并且您还看到建议仅运行一次,之后标志的状态被弄乱了。因此,您的日志记录,跟踪或方面应该执行的任何其他操作都会失败。
现在,我们可以通过使用package de.scrum_master.app;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SomeAspect {
private static boolean isInPointcut = false;
@Pointcut("execution(* *(..)) && if()")
public static boolean allMethodCalls() {
if (isInPointcut)
return false;
isInPointcut = true;
boolean result = PropertyReader.hasSpecificProperty();
isInPointcut = false;
return result;
}
@Pointcut("cflow(adviceexecution()) || within(SomeAspect)")
public void aspectCalls() {}
@Before("allMethodCalls() && !aspectCalls()")
public void logSomething(JoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
PropertyReader.hasSpecificProperty();
}
}
切入点方法Doing something with active property
Doing something with active property
(...)
Doing something with active property
Doing something with active property
Call counter = 13
Duration = 161 ms
Doing something with active property
Doing something with active property
(...)
execution(void de.scrum_master.app.PropertyReader.lambda$0())
execution(void de.scrum_master.app.PropertyReader.doSomething(String))
Doing something with active property
execution(boolean de.scrum_master.app.PropertyReader.hasSpecificProperty())
Call counter = 16
Duration = 190 ms
Exception in thread "main" java.lang.RuntimeException: Call counter delta should be 30, not 3
at de.scrum_master.app.PropertyReader.main(PropertyReader.java:61)
快速修复此问题:
if()
这有效,但是每次synchronized
的调用的运行时间从〜190 ms增加到〜800 ms,即比以前慢4倍:
public static synchronized boolean allMethodCalls(JoinPoint thisJoinPoint)
如果愿意,请尝试一下。现在,经过长时间的解释,我认为您同意doStuff(..)
比简单的Doing something with active property
(...)
Doing something with active property
Call counter = 40
Duration = 821 ms
execution(void de.scrum_master.app.PropertyReader.lambda$0())
(...)
execution(boolean de.scrum_master.app.PropertyReader.hasSpecificProperty())
Call counter = 70
Duration = 802 ms
更好,即使可以通过同步切入点方法使后者起作用。但是只有ThreadLocal
没有同步会破坏该方面,使其成为线程不安全的。