在aspectj类中的哪里编码ThreadLocal.remove() [英] where to code ThreadLocal.remove() in aspectj class

查看:126
本文介绍了在aspectj类中的哪里编码ThreadLocal.remove()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

/* We are using Aspect to do AOP on some existing application and we also used threadlocal to store GUId. we are using @Around annotation. At the start of the transaction we are setting the GUID in transaction with initialValue() method.

问题是众所周知的,当我们使用threadlocal时,我们还应注意从threadlocal中删除数据,否则可能导致内存不足.如果我要删除它 最后一个方面是破坏代码并更改UUID值.

Issue is as we know when we are using threadlocal we should also take care about removing the data from threadlocal otherwise it may result i outofmemory execption. if i am removing it at the last of the aspect it is corrupting the code and changing the UUID value.

请提出在没有内存不足的情况下如何实现这一目标.

Please suggest how we can achieve it without 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;
     }
}

推荐答案

在我们开始一些提示之前:如果编写@Around("logging()"),则切入点方法应从loggingResponseTime()重命名为logging(),否则该方面将不起作用.

Before we start a little hint: If you write @Around("logging()") your pointcut method should be renamed from loggingResponseTime() to actually logging(), otherwise the aspect will not work.

现在要解决您的实际问题:您通过广泛地建议代码来犯一个典型的初学者错误,即,您正在拦截所有方法执行(在JDK之外).如果您使用Eclipse和AJDT,并将光标置于tracing()建议中,则使用当前切入点在AspectJ交叉引用"窗口中将看到类似以下内容:

Now as for your real problem: You are making a typical beginners' mistake by advising code too broadly, i.e. you are intercepting all method executions (outside the JDK). If you use Eclipse and AJDT and you put your cursor into the tracing() advice you will see something like this in the AspectJ "cross reference" window using your current pointcut:

AspectJ交叉引用视图>

您可以立即看到问题:您的切入点捕获了匿名ThreadLocal子类中的代码.这将导致无限递归,最终导致StackOverflowError的出现,如果您对其进行检查的话,就会在自己的调用堆栈中看到.

You can immediately see your problem: Your pointcut captures code in your anonymous ThreadLocal subclass. This leads to endless recursion and finally to the StackOverflowError as you can see in your own callstack if you inspect it.

现在,这里有一些示例代码演示了该问题,供其他人参考:

Now here is some sample code demonstrating the problem for other people's reference:

驱动程序应用程序:

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)
    (...)

那你该怎么办?实际上非常简单:只需从切入点中排除您不想截取的联接点即可.为此,您有几种选择.我只说几句:

So what can you do? It is actually quite simple: Just exclude the joinpoints you do not really want to intercept from your pointcut. For that you have several options. I am just naming a few:

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

A) Put your aspects into a specific package and exclude all (aspect) classes in that package:

@Pointcut("execution(* *(..)) && !within(de.scrum_master.aspect..*)")

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

B) Exclude all classes annotated by @Aspect:

@Pointcut("execution(* *(..)) && !within(@org.aspectj.lang.annotation.Aspect *)")

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

C) Exclude all (aspect) classes matching a certain naming scheme like *Aspect:

@Pointcut("execution(* *(..)) && !within(*..*Aspect)")

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

D) Exclude code from all ThreadLocal subclasses (+ syntax):

@Pointcut("execution(* *(..)) && !within(ThreadLocal+)")

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

In each case the result will be the same:

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!抱歉,但这很膨胀,它会降低代码速度并创建许多不必要的对象.我认为这不明智.

By the way: I have strong doubts about your usage of UUIDs because I see no value in creating expensive objects here. How about just logging timestamps? Why do you need globally unique IDs for logging? They tell you nothing. Furthermore, not only are you creating one ID per thread, but if you use the uncommented id.remove() you even create one per call! Sorry, but this is bloat, it slows down your code and creates lots of unnecessary objects. I do not think this is wise.

更新:

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

I forgot to explain the reason for the endless recursion: Your advice calls ThreadLocal.get(), assuming it could be null. Actually it cannot be because if the value has not been initialised, get() does so by utilising initialValue(). Even if you manually call remove(), the next time you call get() it will again initialise the value again and so forth. This is documented behaviour:

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

那么,逐步发生了什么?

So what happens, step by step?

  • 一个方法被调用.
  • 您周围的建议开始执行.
  • 您从建议中致电id.get().
  • ThreadLocal.get()检查是否设置了一个值,注意到没有设置任何值,并调用了覆盖的initialValue()方法.
  • 由于所有匹配点execution(* *(..))捕获了覆盖的initialValue()方法,因此您的建议会在 设置初始值之前插入.最终结果是循环再次开始,依此类推-无休止的递归,删除演示.
  • A method is called.
  • Your around advice kicks in.
  • You call id.get() from the advice.
  • ThreadLocal.get() checks if a value is set, notices that there is none and calls your overridden initialValue() method.
  • Because the overridden initialValue() method is captured by your match-all pointcut execution(* *(..)), again your advice kicks in before the initial value has been set. The end result is that the loop starts again and so forth - endless recursion, quod erat demonstrandum.

因此,实际上,您的问题归结为从建议中对未初始化的ThreadLocal子类调用get(),同时使用相同的建议同时定位其用户定义的initialValue()方法.这就是创建无穷递归并最终使您的堆栈溢出的原因.

So actually your problem boils down to calling get() on an uninitialised ThreadLocal subclass from an advice while simultaneously targetting its user-defined initialValue() method with the same advice. This is what creates the endless recursion and ultimately makes your stack overflow.

我的建议是从切入点中排除您的方面,请参见上面的示例切入点.您还应该摆脱null检查ThreadLocal值,因为它是多余的.最后但并非最不重要的一点是,我假设您希望每个线程一个ThreadLocal值,而不是每个方法调用一个.因此,您完全不需要任何set()remove()调用.

My recommendation is to exclude your aspect from the pointcut, see example pointcuts above. You should also get rid of the null check for the ThreadLocal value because it is superfluous. Last but not least, I assume you want one ThreadLocal value per thread and not one per method call. So you can do without any set() or remove() calls altogether.

修改后的驱动程序类,创建一个附加线程:

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来实现方面.

As you can see, threads already have unique IDs, so maybe you want consider implementing your aspect without any UUIDs altogether.

这篇关于在aspectj类中的哪里编码ThreadLocal.remove()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆