当前位置: 首页 > news >正文

ThreadLocal加切面实现线程级别的方法缓存

1、实现效果

当一个请求线程多次请求A方法时,只会触发一次A方法的实际调用,会将方法结果缓存起来,避免多次调用。

2、实现过程

1. 需要一个注解ThreadLocalCache,在需要缓存的方法上加上该注解
2. 需要一个切面,借助ThreadLocal,将结果缓存起来,利用环绕通知来实现方法拦截从缓存中返回方法执行结果

3、代码实现

3.1、ThreadLocalCache注解创建

作用于方法级别

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ThreadLocalCache {
}

3.2、ThreadLocalTestAspect切面创建

@Aspect
@Component
public class ThreadLocalTestAspect {
    private ThreadLocal<Map<Object, Object>> threadLocal = new ThreadLocal<>();

    @Around("@annotation(com.example.test.ThreadLocalCache)")
    private Object myPointcut(ProceedingJoinPoint proceedingJoinPoint) {
        //获取方法的入参
        Object[] args = proceedingJoinPoint.getArgs();
        Signature signature = proceedingJoinPoint.getSignature();
        //获取目标方法名
        String name = signature.getName();
        //获取目标方法的类的完全限定名
        String declaringTypeName = signature.getDeclaringTypeName();
        //生成缓存key
        Object key = SimpleKeyGenerator.generateKey(args, declaringTypeName, name);
        if (Objects.isNull(threadLocal.get())) {
            threadLocal.set(new HashMap<>(8));
        }
        try {
            if (!threadLocal.get().containsKey(key)) {
                threadLocal.get().put(key, proceedingJoinPoint.proceed());
            }
        } catch (Throwable e) {
            //日志记录
            e.printStackTrace();
        }
        return threadLocal.get().get(key);
    }

    public void removeThreadLocal(){
        threadLocal.remove();
    }

}

4、测试过程

  1. 创建一个接口及实现
public interface ThreadLocalTestService {

    Long getParentIdByName(String name);
}
@Service
public class ThreadLocalTestServiceImpl implements ThreadLocalTestService{

    @ThreadLocalCache
    @Override
    public Long getParentIdByName(String name) {
        //根据name查询父级ID
        System.out.println("com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了");
        return 666L;
    }
}
  1. 方法调用
@RestController
@RequestMapping("/ThreadLocalTest")
public class ThreadLocalTest {

    @Autowired
    private ThreadLocalTestService threadLocalTestService;

    @Autowired
    private ThreadLocalTestAspect threadLocalTestAspect;


    @GetMapping("getParentIdByName")
    public Long getParentIdByName(String name){
        System.out.println(Thread.currentThread().getName());
        threadLocalTestService.getParentIdByName(name);
        threadLocalTestService.getParentIdByName(name);
        Long parentId = threadLocalTestService.getParentIdByName(name);
        threadLocalTestAspect.removeThreadLocal();
        return parentId;
    }

}

3.执行结果

http-nio-8087-exec-1
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-2
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-4
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-5
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-6
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-7
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-8
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-10
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-9
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-3
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-1
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-2
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了

http-nio-8087-exec-是线程名字,可以看到http-nio-8087-exec-1执行了两次,每次都调用四次getParentIdByName 方法,但getParentIdByName 方法实际至执行了一次,剩下的三次是从缓存中获取的。
这里需要注意的是:线程每次结束的时候都需要调用threadLocalTestAspect.removeThreadLocal();为的是把当前线程threadLocal里的缓存抹掉,因为同一个线程可能会被重复使用,所以不抹掉,可能会导致多次请求使用同一个线程,目标方法只会执行一次,和我们的最初的实现效果是违背的。
下面是不调用threadLocalTestAspect.removeThreadLocal();的执行结果

http-nio-8087-exec-1
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-3
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-8
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-5
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-6
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-7
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-4
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-9
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-10
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-2
com.example.test.ThreadLocalTestServiceImpl.getParentIdByName 执行了
http-nio-8087-exec-1
http-nio-8087-exec-3
http-nio-8087-exec-8
http-nio-8087-exec-5

可以很清楚的看到http-nio-8087-exec-1、3、5、8再次请求的时候getParentIdByName 方法并没有执行了,因为之前的threadlocal缓存没有被remove导致的。

相关文章:

  • QT 线程的使用
  • PDF锐化
  • 【运输层】传输控制协议 TCP
  • easyExcel - 动态复杂表头的编写
  • leetcode热题HOT146. LRU 缓存
  • 【简单讲解下Kotlin】
  • 蓝桥杯 - 受伤的皇后
  • redis的三大模式的演化及集群模式思考和总结
  • Git汇总
  • 【开源书籍】深入讲解内核网络、Kubernetes、ServiceMesh、容器等云原生相关技术。
  • JavaIO输入输出
  • 企业如何设计和实施有效的网络安全演练?
  • 大话设计模式——六大基本设计原则(SOLID原则)
  • 【C语言】扫雷小游戏
  • 路由Vue-Router使用
  • windbg托管内存泄漏排查
  • 魔众 文库配置异步转换
  • 网格矢量如何计算莫兰指数
  • SpringBoot学习之Kibana下载安装和启动(Mac版)(三十二)
  • Mac下Docker Desktop starting的解决方法
  • https://app.hackthebox.com/machines/Inject
  • Spring —— Spring简单的读取和存储对象 Ⅱ
  • 渗透测试之冰蝎实战
  • Mybatis、TKMybatis对比
  • Microsoft Office 2019(2022年10月批量许可版)图文教程
  • 《谷粒商城基础篇》分布式基础环境搭建
  • 哈希表题目:砖墙
  • Vue 3.0 选项 生命周期钩子
  • 【车载嵌入式开发】AutoSar架构入门介绍篇
  • 【计算机视觉 | 目标检测】DETR风格的目标检测框架解读