java - 在aspectj类中的哪里编码ThreadLocal.remove()

标签 java aspectj thread-local aop thread-local-storage

/ *
我们正在使用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()方法作为目标。这就是创建无限递归并最终使堆栈溢出的原因。

我的建议是从切入点中排除您的方面,请参见上面的示例切入点。您还应该摆脱nullThreadLocal值检查,因为它是多余的。最后但并非最不重要的一点是,我假设您希望每个线程一个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/

相关文章:

java - Java接口(interface)的switch语句

java - 如何计算速度模板中列表的大小?

java - 如何将属性注入(inject) Aspect 日志记录类?

java - 如何检查类是否有方面添加的方法?

c++ - clang thread_local 初始化中的错误

java - 完整的垂直线字符 - ncurses 与 Java

java - 检查 Web 服务输入中的问号

java - AspectJ 如何捕获 NoResultException 并返回 null

java - 每个线程一个队列的实现

java - 未引用的 ThreadLocals 是否被初始化?