在aspectj类中编写ThreadLocal.remove()的代码

Vim*_*dav 0 java aop aspectj thread-local thread-local-storage

/*我们使用Aspect在一些现有应用程序上执行AOP,我们还使用threadlocal来存储GUId.我们正在使用@Around注释.在事务开始时,我们使用initialValue()方法在事务中设置GUID.

问题就像我们所知,当我们使用threadlocal时,我们也应该注意从threadlocal中删除数据,否则它可能导致我的内存执行.如果我在最后一个方面删除它,它会破坏代码并更改UUID值.

请建议我们如何在没有outofmemory的情况下实现它.

代码: - */

@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;
     }
}
Run Code Online (Sandbox Code Playgroud)

kri*_*aex 5

在我们开始一点提示之前:如果你写了@Around("logging()")你的切入点方法应该从loggingResponseTime()实际重命名logging(),否则方面将无法工作.

至于你真正的问题:你通过过于宽泛地建议代码来制造典型的初学者错误,即你正在拦截所有方法执行(在JDK之外).如果您使用Eclipse和AJDT,并将光标放在tracing()建议中,您将在AspectJ"交叉引用"窗口中使用当前切入点看到类似的内容:

<code>ThreadLocal</code>子类中的代码.这导致无休止的递归,最后到<code>StackOverflowError</code>你自己的callstack中可以看到,如果你检查它.</p>

<p>现在这里有一些示例代码,展示了其他人的参考问题:</p>

<p><strong>司机申请:</strong></p>

<pre class=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"; } } Run Code Online (Sandbox Code Playgroud)

方面:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

控制台输出:

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)
    (...)
Run Code Online (Sandbox Code Playgroud)

所以,你可以做什么?它实际上非常简单:只需从切入点中排除您不想真正拦截的连接点.为此你有几个选择.我只是命名一些:

A)将您的方面放入特定包中并排除该包中的所有(方面)类:

@Pointcut("execution(* *(..)) && !within(de.scrum_master.aspect..*)")
Run Code Online (Sandbox Code Playgroud)

B)排除所有注释的类@Aspect:

@Pointcut("execution(* *(..)) && !within(@org.aspectj.lang.annotation.Aspect *)")
Run Code Online (Sandbox Code Playgroud)

C)排除匹配某个命名方案的所有(方面)类,如*Aspect:

@Pointcut("execution(* *(..)) && !within(*..*Aspect)")
Run Code Online (Sandbox Code Playgroud)

D)从所有ThreadLocal子类中排除代码(+语法):

@Pointcut("execution(* *(..)) && !within(ThreadLocal+)")
Run Code Online (Sandbox Code Playgroud)

在每种情况下,结果都是相同的:

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[])
Run Code Online (Sandbox Code Playgroud)

顺便说一下:我对你对UUIDs 的使用有很强的怀疑,因为我认为在这里创建昂贵的对象毫无价值.如何记录时间戳?为什么需要全局唯一ID进行日志记录?他们什么也没告诉你.此外,您不仅要为每个线程创建一个ID,而且如果您使用未注释的,id.remove()您甚至可以为每个呼叫创建一个ID !对不起,但这很臃肿,它会降低代码速度并产生大量不必要的对象.我不认为这是明智的.


更新:

我忘了解释无限递归的原因:你的建议调用ThreadLocal.get(),假设它可能为空.实际上它不可能是因为如果价值尚未初始化,get()则通过利用来实现initialValue().即使您手动呼叫remove(),下次调用get()它时也会再次初始化该值,依此类推.这是记录在案的行为:

返回当前线程的此线程局部变量副本中的值.如果变量没有当前线程的值,则首先将其初始化为调用initialValue()方法返回的值.

那么,一步一步地发生了什么呢?

  • 调用一种方法.
  • 你周围的建议开始了.
  • id.get()从建议中打来电话.
  • ThreadLocal.get()检查是否设置了值,注意到没有值并调用被覆盖的initialValue()方法.
  • 因为initialValue()匹配所有切入点捕获了重写方法execution(* *(..)),所以设置初始值之前,您的建议再次启动.最终的结果是循环再次开始 - 等等 - 无休止的递归,quod erat demonstrandum.

所以实际上你的问题归结为从一个建议中调用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";
    }
}
Run Code Online (Sandbox Code Playgroud)

改进方面:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

控制台输出:

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[])
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,线程已经拥有唯一的ID,因此您可能需要考虑在没有任何UUID的情况下实现您的方面.