/ *
我们正在使用Aspect在某些现有应用程序上执行AOP,并且还使用threadlocal存储GUId。我们正在使用@Around注释。
在事务开始时,我们使用initialValue()方法在事务中设置GUID。
众所周知,问题是当我们使用threadlocal时,我们还应注意从threadlocal中删除数据,否则可能导致内存不足执行。如果我要删除它
最后一个方面是破坏代码并更改UUID值。
请提出我们如何在没有内存不足的情况下实现它。
代码:-
* /
@Aspect
public class DemoAspect {
@Pointcut("execution(* *.*(..)) ")
public void logging() {}
private static ThreadLocal<String> id = new ThreadLocal<String>() {
@Override
protected String initialValue(){
return UUID.randomUUID().toString();
}
};
@Around("logging()")
public Object tracing(ProceedingJoinPoint thisJoinPoint) throws Throwable {
String methodSignature=thisJoinPoint.getSignature().toString();
if(id.get().toString()==null || id.get().toString().length()==0)
id.set(UUID.randomUUID().toString());
System.out.println("Entering into "+methodSignature);
Object ret = thisJoinPoint.proceed();
System.out.println(id.get().toString());
System.out.println("Exiting into "+methodSignature);
//id.remove();
return ret;
}
}
最佳答案
在我们开始之前,有一些提示:如果编写@Around("logging()")
,则切入点方法应从loggingResponseTime()
重命名为实际的logging()
,否则该方面将不起作用。
现在,就您的真正问题:通过广泛地建议代码来犯一个典型的初学者错误,即您正在拦截所有方法执行(在JDK之外)。如果使用Eclipse和AJDT,并将光标置于tracing()
建议中,则使用当前切入点在AspectJ“交叉引用”窗口中将看到类似以下内容:
您可以立即看到问题:您的切入点捕获了匿名ThreadLocal
子类中的代码。这导致了无穷的递归,最终导致了StackOverflowError
,如果您检查它,您将在自己的调用堆栈中看到。
现在,这里有一些示例代码演示了该问题,供其他人参考:
驱动程序应用程序:
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
System.out.println(bar(foo()));
}
public static String bar(String text) {
return text + "bar";
}
private static String foo() {
return "foo";
}
}
方面:
package de.scrum_master.aspect;
import java.util.UUID;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class DemoAspect {
private static ThreadLocal<String> id = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return UUID.randomUUID().toString();
}
};
@Pointcut("execution(* *(..))")
public void logging() {}
@Around("logging()")
public Object tracing(ProceedingJoinPoint thisJoinPoint) throws Throwable {
String methodSignature = thisJoinPoint.getSignature().toString();
if (id.get().toString() == null || id.get().toString().length() == 0)
id.set(UUID.randomUUID().toString());
System.out.println("Entering into " + methodSignature);
Object ret = thisJoinPoint.proceed();
System.out.println(id.get().toString());
System.out.println("Exiting from " + methodSignature);
id.remove();
return ret;
}
}
控制台输出:
Exception in thread "main" java.lang.StackOverflowError
at org.aspectj.runtime.reflect.SignatureImpl$CacheImpl.get(SignatureImpl.java:217)
at org.aspectj.runtime.reflect.SignatureImpl.toString(SignatureImpl.java:50)
at org.aspectj.runtime.reflect.SignatureImpl.toString(SignatureImpl.java:62)
at de.scrum_master.aspect.DemoAspect$1.initialValue_aroundBody1$advice(DemoAspect.aj:29)
at de.scrum_master.aspect.DemoAspect$1.initialValue(DemoAspect.aj:1)
at de.scrum_master.aspect.DemoAspect$1.initialValue(DemoAspect.aj:1)
at java.lang.ThreadLocal.setInitialValue(ThreadLocal.java:160)
at java.lang.ThreadLocal.get(ThreadLocal.java:150)
at de.scrum_master.aspect.DemoAspect$1.initialValue_aroundBody1$advice(DemoAspect.aj:30)
at de.scrum_master.aspect.DemoAspect$1.initialValue(DemoAspect.aj:1)
at de.scrum_master.aspect.DemoAspect$1.initialValue(DemoAspect.aj:1)
at java.lang.ThreadLocal.setInitialValue(ThreadLocal.java:160)
at java.lang.ThreadLocal.get(ThreadLocal.java:150)
(...)
所以,你可以做什么?实际上非常简单:只需从切入点中排除您不想截取的联接点即可。为此,您有几种选择。我只说几句:
A)将您的方面放入特定的程序包中,并排除该程序包中的所有(方面)类:
@Pointcut("execution(* *(..)) && !within(de.scrum_master.aspect..*)")
B)排除所有用
@Aspect
注释的类:@Pointcut("execution(* *(..)) && !within(@org.aspectj.lang.annotation.Aspect *)")
C)排除所有与某些命名方案(如
*Aspect
)匹配的(方面)类:@Pointcut("execution(* *(..)) && !within(*..*Aspect)")
D)从所有
ThreadLocal
子类中排除代码(+
语法):@Pointcut("execution(* *(..)) && !within(ThreadLocal+)")
在每种情况下,结果都是相同的:
Entering into void de.scrum_master.app.Application.main(String[])
Entering into String de.scrum_master.app.Application.foo()
d2b83f5f-7282-4c06-9b81-6601c8e0499d
Exiting from String de.scrum_master.app.Application.foo()
Entering into String de.scrum_master.app.Application.bar(String)
0d1c9463-4bbd-427d-9d64-c7f3967756cf
Exiting from String de.scrum_master.app.Application.bar(String)
foobar
aa96bbbd-a1a1-450f-ae6e-77ab204c5fb2
Exiting from void de.scrum_master.app.Application.main(String[])
顺便说一句:我强烈怀疑您使用
UUID
的原因,因为在这里创建昂贵的对象没有任何价值。只记录时间戳怎么样?为什么需要全局唯一的ID进行日志记录?他们什么都没告诉你。此外,不仅您在每个线程上创建一个ID,而且如果您使用未注释的id.remove()
甚至在每个调用上创建一个ID!抱歉,但这很膨胀,它会降低代码速度并创建许多不必要的对象。我认为这不明智。更新:
我忘了解释无限递归的原因:您的建议调用
ThreadLocal.get()
,假定它可能为空。实际上,这不是因为如果尚未初始化值,get()
会利用initialValue()
进行初始化。即使您手动调用remove()
,下次调用get()
时,它将再次再次初始化该值,依此类推。这是documented behaviour:返回此线程局部变量的当前线程副本中的值。如果该变量没有当前线程的值,则首先将其初始化为调用
initialValue()
方法返回的值。那么,逐步发生了什么?
称为方法。
您的周围建议开始生效。
您从建议中呼叫
id.get()
。ThreadLocal.get()
检查是否设置了值,发现没有设置值,并调用覆盖的initialValue()
方法。由于覆盖的
initialValue()
方法是由您所有匹配的切入点execution(* *(..))
捕获的,因此您的建议会在设置初始值之前再次加入。最终结果是循环再次开始,依此类推-无休止的递归,删除演示。因此,实际上,您的问题归结为从建议中对未初始化的
get()
子类调用ThreadLocal
,同时使用相同的建议将其用户定义的initialValue()
方法作为目标。这就是创建无限递归并最终使堆栈溢出的原因。我的建议是从切入点中排除您的方面,请参见上面的示例切入点。您还应该摆脱
null
的ThreadLocal
值检查,因为它是多余的。最后但并非最不重要的一点是,我假设您希望每个线程一个ThreadLocal
值,而不是每个方法调用一个。因此,您可以完全不用任何set()
或remove()
调用。修改了驱动程序类,创建了一个附加线程:
package de.scrum_master.app;
public class Application {
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(bar(foo()));
}
}).start();
Thread.sleep(200);
}
public static String bar(String text) {
return text + "bar";
}
private static String foo() {
return "foo";
}
}
改进的方面:
package de.scrum_master.aspect;
import java.util.UUID;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class DemoAspect {
private static ThreadLocal<UUID> id = new ThreadLocal<UUID>() {
@Override
protected UUID initialValue() {
return UUID.randomUUID();
}
};
@Pointcut("execution(* *(..)) && !within(DemoAspect)")
public void logging() {}
@Around("logging()")
public Object tracing(ProceedingJoinPoint thisJoinPoint) throws Throwable {
Signature methodSignature = thisJoinPoint.getSignature();
System.out.println(
"Thread " + Thread.currentThread().getId() +
"[" + id.get() +
"] >>> " + methodSignature
);
Object result = thisJoinPoint.proceed();
System.out.println(
"Thread " + Thread.currentThread().getId() +
"[" + id.get() +
"] <<< " + methodSignature
);
return result;
}
}
控制台输出:
Thread 1[549d0856-0a92-4031-9331-a1317d6a43c4] >>> void de.scrum_master.app.Application.main(String[])
Thread 9[32c8444c-0f1f-4023-9b97-69d5beda3b4c] >>> void de.scrum_master.app.Application.1.run()
Thread 9[32c8444c-0f1f-4023-9b97-69d5beda3b4c] >>> String de.scrum_master.app.Application.access$0()
Thread 9[32c8444c-0f1f-4023-9b97-69d5beda3b4c] >>> String de.scrum_master.app.Application.foo()
Thread 9[32c8444c-0f1f-4023-9b97-69d5beda3b4c] <<< String de.scrum_master.app.Application.foo()
Thread 9[32c8444c-0f1f-4023-9b97-69d5beda3b4c] <<< String de.scrum_master.app.Application.access$0()
Thread 9[32c8444c-0f1f-4023-9b97-69d5beda3b4c] >>> String de.scrum_master.app.Application.bar(String)
Thread 9[32c8444c-0f1f-4023-9b97-69d5beda3b4c] <<< String de.scrum_master.app.Application.bar(String)
foobar
Thread 9[32c8444c-0f1f-4023-9b97-69d5beda3b4c] <<< void de.scrum_master.app.Application.1.run()
Thread 1[549d0856-0a92-4031-9331-a1317d6a43c4] <<< void de.scrum_master.app.Application.main(String[])
如您所见,线程已经具有唯一的ID,所以也许您想考虑完全不使用任何UUID来实现方面。
关于java - 在aspectj类中的哪里编码ThreadLocal.remove(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25663050/