当前位置: 首页 > 知识库问答 >
问题:

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

伍玮
2023-03-14

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

问题是,当我们使用threadlocal时,我们还应该注意从threadlocale中删除数据,否则可能会导致内存执行失败。如果我在方面的最后删除它,它会破坏代码并更改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;
     }
}

共有1个答案

顾正初
2023-03-14

在开始之前,我们先给出一个提示:如果您编写@Around(“logging()”),那么您的切入点方法应该从loggingResponseTime()重命名为实际上的0.05。

现在,至于你真正的问题:你犯了一个典型的初学者的错误,建议代码太广泛,也就是说,你拦截了所有的方法执行(在JDK之外)。如果您使用Eclipse和AJDT,并将光标放在< code>tracing()建议上,您将在AspectJ“交叉引用”窗口中看到类似这样的内容,使用您当前的切入点:

您可以立即看到您的问题:您的切入点捕获匿名ThreadLocal子类中的代码。这会导致无休止的递归,最终导致StackOverflow Error,如果您检查它,您可以在自己的调用堆栈中看到。

下面是一些演示该问题的示例代码,供其他人参考:

驱动程序应用:

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) 排除与特定命名方案匹配的所有(方面)类,如*方面

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

d)从所有< code>ThreadLocal子类中排除代码(< code> 语法):

@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()您甚至可以为每个调用创建一个!对不起,这是膨胀的,它减慢了您的代码并创建了许多不必要的对象。我认为这不明智。

更新:

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

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

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

    < li >调用一个方法。 < li >你的建议生效了。 < li >您从建议中调用< code>id.get()。 < Li > < code > thread local . get()检查是否设置了值,注意到没有值,并调用您重写的< code>initialValue()方法。 < li >因为被覆盖的< code>initialValue()方法是由match-all切入点< code >执行(* *(..)),您的建议在初始值设定之前再次生效。最终的结果是循环再次开始,如此循环往复——无止境的递归,quod erat demonstrandum。

因此,实际上,您的问题归结为从一个通知中对一个未初始化的ThreadLocal子类调用get()>,同时使用相同的通知来获取其用户定义的 initialValue()方法。这就是造成无休止递归并最终导致堆栈溢出的原因。

我的建议是从切入点中排除您的方面,请参阅上面的切入点示例。您还应该去掉null检查ThreadLocalvalue,因为它是多余的。最后,我假设每个线程需要一个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的情况下实现您的方面。

 类似资料:
  • 我想将我的VBA代码实现到SAS代码中,这样一次运行就可以完成整个过程。我的SAS代码读取一个大SAS表,进行一些转换,最后导出到Excel文件(代码如下)。我还在Excel文件中编写了一些VBA代码(例如,对某些变量进行自动筛选,您可以看到下面的代码)。 这张桌子看起来像这样: 然而,我想将我的VBA代码实现到SAS代码中,这样我就可以一次运行完成整个过程。我知道如何在SAS中打开和运行Exce

  • 了解如何在“代码”视图中工作并充分利用 Dreamweaver 的编码功能。 可通过多种方式在 Dreamweaver 中处理代码。 您可以使用“新建文档”对话框打开新的代码文件,然后开始键入您的代码。在 Dreamweaver 中创建新的代码文件 键入时,会显示代码提示以帮助您选择代码和避免打字错误。如果需要,可使用 Dreamweaver 的有用的快捷文档获取 CSS 的相关帮助。 还可以使用

  • 我试图使用VS Code,但我有一个问题,打开与其他编辑器编写的代码,VS Code不能阅读韩语Unicode?UTF-8?我们在代码中称其他语言为什么? 我用vim编辑器编写了代码,其中一些注释是用韩语编写的,任何其他编辑器都可以阅读韩语,但VS代码如下所示。 我怎样才能解决这个问题?

  • 问题内容: 我可以很轻松地在Node.js中编写非阻塞I / O。 这就是整个库的用途。 但是所做的任何计算都是阻塞的。任何通过事件发送器的消息都将被阻止。 例如,发出事件立即得到解决,因此被阻止: 除了将调用包装起来外,如何使代码无阻塞? 我希望在事件循环的每个周期中进行尽可能少的计算,以便可以同时为尽可能多的客户端提供服务。 如何以非阻塞方式编写代码? 当我拥有非阻塞代码时,如何在多个进程之间

  • 问题内容: 我想评估 作为一个块,而不是逐行评估 有没有简单的方法可以将提示移至下一行? 问题答案: 节点v6.4具有一种模式。在repl提示符下,您可以输入多行。 例 以下是所有特殊repl命令的文档 https://nodejs.org/api/repl.html#repl_commands_and_special_keys

  • 11.3 编写代码 要完成我们的程序,我们需要创建一个Java文件。默认情况下,Maven会编译src/main/java目录下的源文件,因此您需要创建该目录结构,然后添加一个名为src/main/java/Example.java的文件: import org.springframework.boot.*; import org.springframework.boot.autoconfigur