在aspectj 类中编写ThreadLocal.remove() 的位置 [英] where to code ThreadLocal.remove() in aspectj class

查看:26
本文介绍了在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(),否则aspect将无法工作.

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:

您可以立即看到您的问题:您的切入点捕获匿名 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(),您甚至每次调用都会创建一个!抱歉,但这很臃肿,它会减慢您的代码速度并创建许多不必要的对象.我不认为这是明智的.

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() 方法返回的值.

Returns the value in the current thread's copy of this thread-local variable. If the variable has no value for the current thread, it is first initialized to the value returned by an invocation of the initialValue() method.

那么会发生什么,一步一步来?

So what happens, step by step?

  • 一个方法被调用.
  • 您的周围建议开始发挥作用.
  • 您从通知中调用 id.get().
  • ThreadLocal.get() 检查是否设置了值,注意没有设置并调用您重写的 initialValue() 方法.
  • 因为覆盖的 initialValue() 方法被你的匹配所有切入点 execution(* *(..)) 捕获,你的建议再次启动 在设置初始值之前.最终结果是循环再次开始,以此类推——无休止的递归,quoderat demostrandum.
  • 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.

我的建议是从切入点中排除您的方面,请参阅上面的示例切入点.您还应该取消对 ThreadLocal 值的 null 检查,因为它是多余的.最后但并非最不重要的一点是,我假设您需要每个线程一个 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天全站免登陆