springboot实战学习(10)(ThreadLoacl优化获取用户详细信息接口)(重写拦截器afterCompletion()方法)

  • 接着学习。之前的博客的进度:完成用户模块的注册接口的开发以及注册时的参数合法性校验、也基本完成用户模块的登录接口的主逻辑的基础上、JWT令牌"的组成与使用、完成了"登录认证"(生成与验证JWT令牌)以及完成获取用户详细信息接口开发。具体往回看了解的链接如下。

springboot实战学习(9)(配置mybatis“驼峰命名“和“下划线命名“自动转换)(postman接口测试统一添加请求头)(获取用户详细信息接口)_springboot 的实体类实现下划线命名?-CSDN博客文章浏览阅读760次,点赞10次,收藏23次。这篇博客主要是完成用户模块的“获取用户详细信息”接口开发。其中包括读取请求头中的“JWT令牌”并解析获取用户名、在postman接口测试统一添加请求头以及在.yml文件中配置mybatis"驼峰命名"和"下划线命名"自动转换..._springboot 的实体类实现下划线命名?https://blog.csdn.net/m0_74363339/article/details/142531404?spm=1001.2014.3001.5501

  • 但是在获取用户详细信息的接口开发的代码还有优化的空间。本篇博客是学习借助ThreadLocal来优化代码。

目录

一、问题与分析

(1)查看UserController类查看之前写的"获取用户详细信息"接口的代码。

(2)查看之前在拦截器里面也写过解析token令牌的代码。

(3)问题

二、ThreadLocal

(1)基本作用

(2)举例

(3)IDAE中操作演示

(I)创建一个测试类"ThreadLocalTest"。

(II)提供测试方法"testThreadLocalSetAndGet()",添加注解@Test。

(III)完善方法内部。

Lambda表达式。

(IIII)测试。

(4)联系与思考

(I)初步解决方法

(II)问题

(III)结论

三、ThreadLocal优化用户详细信息接口。

(1)回到UserController层的"/userInfo"接口。

(2)使用一个ThreadLocal工具类。

(3)回到拦截器LoginInterceptor中。

(4)再次回到UserController层的"/userInfo"接口。

(5)重启工程进行接口测试。

(6)问题。

(I)分析。

(II)重写拦截器中的afterCompletion()方法。

(III)整个拦截器的代码

(7)再次重启工程进行接口测试。没啥问题。

四、总结

(1)使用ThreadLocal需要注意的地方。


一、问题与分析

(1)查看UserController类查看之前写的"获取用户详细信息"接口的代码。

(2)查看之前在拦截器里面也写过解析token令牌的代码。

(3)问题
  • 在其它地方也需要使用用户信息的时候。这个时候代码就不一定只重复一次了。
  • 所以既然在拦截器中写了同样的代码,那么在"/userInfo"接口里面不在写了。然后参数也不在声明了、解析token的代码也不写了。
  • 而是复用拦截器里面去解析得到的结果。如何做到?ThreadLocal

二、ThreadLocal

(1)基本作用
  • 提供线程局部变量
  • 提供了方法用来存储数据:set()方法、get()方法。
  • 使用ThreadLocal存储的数据,线程安全(像局部变量一样。每个线程属于自己,互不影响)
(2)举例
  • 如下有一个ThreadLocal对象tl。然后又有两个线程:"蓝色线程"与"绿色线程"。它们都持有ThreadLoca tl这个对象的引用。
  • 在这两个线程都能调用set()方法存储用户名。它们分别存储了"萧炎"、"药尘"。
  • 在"蓝色线程"中调用get()方法获取名字时,只能获取到"萧炎"。
  • 因为ThreadLocal分别为两个线程创建存储数据的空间。可以做到线程隔离

(3)IDAE中操作演示
(I)创建一个测试类"ThreadLocalTest"。

(II)提供测试方法"testThreadLocalSetAndGet()",添加注解@Test

(III)完善方法内部。
  • 提供一个ThreadLocal对象
  • 开启两个线程。
  • 开启线程:"new Thread()"。然后调用"start()"方法开启线程。"new Thread()"可以传递两个参数。分别是线程任务与线程名字。而线程任务的Runnable对象用Lambda表达式来给他提供。再用"逗号"后面填写另外一个参数name的值("蓝色"、"绿色")。
  • 线程任务:首先第一个线程调用"ThreadLocal对象tl"的set()方法存一个用户名"萧炎"。再调用"get()"方法获取当前线程里面存的用户名。并将获取到用户名输出到控制台。然后第二个线程调用"ThreadLocal对象tl"的set()方法存一个用户名"药尘"。再调用"get()"方法获取当前线程里面存的用户名。并将获取到用户名输出到控制台。
  • 为了区分,在输出加一个字符串"Thread.currentThread().getName()",并且输出三次(方便看)
package com.feisi;
import org.junit.jupiter.api.Test;public class ThreadLocalTest {@Testpublic void testThreadLocalSetAndGet(){//提供一个ThreadLocal对象ThreadLocal tl = new ThreadLocal();//开启两个线程//第一个线程new Thread(()->{//Lambda表达式写线程任务tl.set("萧炎");System.out.println(Thread.currentThread().getName()+":"+tl.get());System.out.println(Thread.currentThread().getName()+":"+tl.get());System.out.println(Thread.currentThread().getName()+":"+tl.get());},"蓝色").start();//第二个线程new Thread(()->{//Lambda表达式写线程任务tl.set("药尘");System.out.println(Thread.currentThread().getName()+":"+tl.get());System.out.println(Thread.currentThread().getName()+":"+tl.get());System.out.println(Thread.currentThread().getName()+":"+tl.get());},"绿色").start();}
}
  • Lambda表达式。
  • Lambda表达式由参数列表、箭头符号('->')和方法体组成。其中方法体既可以是一个表达式,也可以是一个语句块
  • 其中,表达式会被执行,然后返回执行结果;语句块中的语句会被依次执行,就像方法中的语句一样。
(IIII)测试。
  • 注意。现在用的是同一个ThreadLocal对象来存储和获取数据。所以要测试:重点查看"蓝色"线程获取的是不是"萧炎",而"绿色"线程获取的是不是"药尘"。
  • 最后发现它会两个线程都开辟了存储空间,做到了线程隔离。
  • 因为线程分配随机执行,所以执行顺序不一定有序。

(4)联系与思考
(I)初步解决方法
  • 我们可以维护一个全局的ThreadLocal对象,用来存储用户名、用户id这类数据。
  • 我们可以在请求到达拦截器之后,调用这个ThreadLocal对象的set()方法来存储用户的id。
  • 然后当请求到达Controller、Service、Dao层的时候,它们的方法内部只要有需要,就可以调用tl.get()方法获取到用户id,然后去使用。
(II)问题
  • Controller、Service、Dao它们一般在容器中是单例的。当获取用户id的时候,怎么让它们知道当前需要获取用户的id是哪个?会不会发生线程安全的问题?(get()方法获取id)
  • 举例,有两个用户去访问该程序。他们携带的userId分别为"1"和"2"。当请求到达Tomcat时,服务器会为每一个用户开辟一个线程,用来提供服务。
  • 补充。将Controller、Service、Dao设计为单例可以显著提高系统的性能和效率。但需要注意线程安全性问题。如果组件中包含状态信息或共享资源,则需要采取适当的措施来确保线程安全。
(III)结论
  • 通过分析,可以大致得出借助ThreadLocal可以做两件事情。
  • 第一件事情。减少参数的传递,方法中的参数不需要重复声明了。
  • 第二件事情。可以在同一个线程的执行代码间,进行共享数据。比如把拦截器中的数据,把它共享到Controller、Service、Dao层等进行使用。

接下来,借助ThreadLocal将之前写的代码进行优化。

三、ThreadLocal优化用户详细信息接口。

回到IDEA中

(1)回到UserController层的"/userInfo"接口。
  • 注释掉之前写的方法参数(请求头)。
  • 注释掉token解析代码。

(2)使用一个ThreadLocal工具类。
  • 为了使用方便,我使用了一个ThreadLocal的工具类。将类复制到utils包下。

  • 首先看到下面的工具类。它提供了一个常量"THREAD_LOCAL"。这个就是用来维护一个全局唯一的ThreadLocal对象。
  • 然后有一个get()方法,把得到的数据返回回去。而且还是用的是一个泛型。(如果声明的是String,它会强转为String。声明的Map,强转Map)因为ThreadLocal可以存储任意类型的数据。
  • 还提供了一个set()方法,存储值调用set()方法。
  • 注意:提供了一个remove()方法,它就是调用了ThreadLocal中的remove()方法。它是用来清除之前存的数据。因为ThreadLocal设定的时候是唯一的(全局变量),那么它的生命周期特别的长。如果用完了不清除,就会一直驻留,可能造成内存泄漏问题。
package com.feisi.utils;import java.util.HashMap;
import java.util.Map;/*** ThreadLocal 工具类*/
@SuppressWarnings("all")
public class ThreadLocalUtil {//提供ThreadLocal对象,private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();//根据键获取值public static <T> T get(){return (T) THREAD_LOCAL.get();}//存储键值对public static void set(Object value){THREAD_LOCAL.set(value);}//清除ThreadLocal 防止内存泄漏public static void remove(){THREAD_LOCAL.remove();}
}
(3)回到拦截器LoginInterceptor中。
  • 在解析得到的业务数据后,将业务数据存储到ThreadLocal中。
  • 利用到上面的ThreadLocal工具类。

(4)再次回到UserController层的"/userInfo"接口。
  • 刚刚在拦截器存储的值是Map类型的。
  • 现在通过get()方法获取,并用变量接收即可。
  • 再获取指定的属性"username"即可。
  @GetMapping("/userInfo")public Result<User> userInfo(/*@RequestHeader(name = "Authorization") String token*/){//方法内部根据用户名查询用户/* Map<String, Object> map = JwtUtil.parseToken(token);String username = map.get("username").toString();*/Map<String,Object> map = ThreadLocalUtil.get();String username = map.get("username").toString();//拿到username就去调用service层的据用户名查询用户方法User user = userService.findByName(username);return Result.success(user);}
(5)重启工程进行接口测试。
  • 注意。登录认证生成的"JWT令牌"可能测试时已经过期,之前设定的是12个小时。所以在postman中测试接口时,需要重新生成一个"JWT令牌",然后在去统一设置请求头。

  • 再次测试接口成功获取数据。

访问"localhost:8080/user/userInfo"

(6)问题。
(I)分析。
  • 当我们用完数据要记得去清除这个数据。
  • 应该在哪个位置去清楚???(remove()方法
  • 分析:拦截器中,解析成功携带的token令牌后,将它存储到ThreadLocal中。当请求放行了(return了true)之后。在Controller、Service、Dao层中都可以使用到这个共享数据。当响应完成了之后,也就是这一次请求结束了就不再使用了。
  • 所以我们应该是请求完成了,然后把数据清除掉。
(II)重写拦截器中的afterCompletion()方法。
@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//清空ThreadLocal中的数据//防止内存泄漏ThreadLocalUtil.remove();}
(III)整个拦截器的代码
package com.feisi.interceptors;import com.feisi.pojo.Result;
import com.feisi.utils.JwtUtil;
import com.feisi.utils.ThreadLocalUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import java.util.Map;@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//令牌验证String token = request.getHeader("Authorization");//解析token//用提供的工具类解析和验证tokentry {Map<String, Object> claims = JwtUtil.parseToken(token);//将得到的业务数据存储到ThreadLocal中ThreadLocalUtil.set(claims);//放行return true;} catch (Exception e) {//设置http响应状态码为401response.setStatus(401);//不放行return false;}}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//清空ThreadLocal中的数据//防止内存泄漏ThreadLocalUtil.remove();}
}
(7)再次重启工程进行接口测试。没啥问题。

四、总结

(1)使用ThreadLocal需要注意的地方。
  • 用来存取数据:set()/get()方法。
  • 使用ThreadLocal存储的数据,是线程安全的。
  • 用完之后一定记得remove()方法释放。防止内存泄漏!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1551621.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Java中异常的认识和创建

文章目录 前言一、异常的概念与体系结构 1.1 异常的概念1.2 异常的体系结构1.3 异常的分类二、异常的处理 2.1.防御式编程2.2 异常的抛出2.3 异常的捕获2.4 异常的处理流程三、自定义异常类 一、异常的概念与体系结构 1.1 异常的概念 在生活中&#xff0c;一个人表情痛苦&…

51单片机的智能垃圾桶【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块超声波传感器红外传感器步进电机按键、蜂鸣器、LED等模块构成。适用于智能自动感应垃圾桶、超声波智能垃圾桶等相似项目。 可实现基本功能: 1、LCD1602实时显示桶内垃圾高度 2、超声波传感器采集桶顶到垃圾的距离…

Temporal Dynamic Quantization for Diffusion Models阅读

文章目录 AbstractIntroductionBackgrounds and Related Works2.1 扩散模型2.2 量化2.3 量化感知训练和训练后量化 TemporalDynamic Quantization3.1 量化方法3.2 扩散模型量化的挑战3.3 TDQ模块的实现3.4 工程细节时间步的频率编码TDQ模块的初始化 Experimental SetupResults5…

C99中的变长数组

在C99标准之前&#xff0c;从语言在创建数组的时候&#xff0c;指定数组的大小只能使用常量和表达式&#xff0c;或者数据初始化的时候&#xff0c;可以省略数组大小。 1.int arr[5]{1,2,3,4,4}; 2.int arr[24]{1,2,3,4,5,6}; 3.int arr[]{1,2,3,3,4,5,6}; 这样的语法限制不够灵…

使用 ModelScope Studio 实现可滚动 ChatBot

前面的文章提到过 Gradio 的 Chatbot 不能自动滚动的问题&#xff0c;最近看到了 ModelScope Studio 扩展的一些组件&#xff0c;其中 Chatbot 就完美解决了自动滚动的问题&#xff0c;同时还增加了很多更细化的功能&#xff0c;例如可以设置用户和 AI 的头像。官方文档&#x…

QCamera6.7笔记

1.QCamera​ .h文件 #include <QtWidgets/QMainWindow> #include "ui_QCamera_test1.h" #include <QCamera> #include <QtMultimedia> #include <QtMultimediaWidgets> #include<QMediaCaptureSession> #include <QMediaDevices&…

渗透测试之密码暴力破解工具medusa美杜莎

《网安面试指南》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484339&idx1&sn356300f169de74e7a778b04bfbbbd0ab&chksmc0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene21#wechat_redirect 《Java代码审…

C语言指针详解与应用(不断更新)

指针简介 指针(Pointer)是C语言的一个重要知识点&#xff0c;其使用灵活、功能强大&#xff0c;是C语言的灵魂 指针与底层硬件联系紧密&#xff0c;使用指针可操作数据的地址&#xff0c;实现数据的间接访问 指针生活实例化 指针的本质是地址&#xff0c;在生活中比如你取快…

C++快速入门

文章目录 C快速入门一、命名空间1.初始C2.概念3.命名空间的定义1.普通的命名空间2.命名空间的嵌套3.命名空间的重名问题3.命名空间的展开 二、C的输入&输出三、缺省参数1.全缺省参数2.半缺省参数3.缺省参数的用途4.缺省参数的注意点 四、函数重载1.函数重载的原则2.以下的函…

【RocketMQ】RocketMQ应用难点

&#x1f3af; 导读&#xff1a;本文探讨了RocketMQ中消息重复消费的问题及其解决方案&#xff0c;尤其是在CLUSTERING模式下的扩容影响。文章分析了重复消费的原因&#xff0c;如广播模式、负载均衡模式下的多consumerGroup消费、消费者组内的动态变化及网络延迟等&#xff0c…

婚恋交友系统该如何做才能做到日进斗金?

要使婚恋交友系统实现盈利并做到日进斗金&#xff0c;需要综合考虑市场需求、用户体验、商业模式和营销策略等多个方面。以下是一些建议&#xff0c;旨在帮助构建一个成功且盈利的婚恋交友系统&#xff1a; 深入了解目标市场&#xff1a; 研究目标用户群体的需求、偏好和行为模…

这 5 个自动化运维场景,可能用 Python 更香?

许多运维工程师会使用 Python 脚本来自动化运维任务。Python 是一种流行的编程语言&#xff0c;具有丰富的第三方库和强大的自动化能力&#xff0c;适用于许多不同的领域。 这里插播一条粉丝福利&#xff0c;如果你正在学习Python或者有计划学习Python&#xff0c;想要突破自我…

10款好用的开源 HarmonyOS 工具库

大家好&#xff0c;我是 V 哥&#xff0c;今天给大家分享10款好用的 HarmonyOS的工具库&#xff0c;在开发鸿蒙应用时可以用下&#xff0c;好用的工具可以简化代码&#xff0c;让你写出优雅的应用来。废话不多说&#xff0c;马上开整。 1. efTool efTool是一个功能丰富且易用…

YOLO11震撼发布!

非常高兴地向大家介绍 Ultralytics YOLO系列的新模型&#xff1a; YOLO11&#xff01; YOLO11 在以往 YOLO 模型基础上带来了一系列强大的功能和优化&#xff0c;使其速度更快、更准确、用途更广泛。主要改进包括 增强了特征提取功能&#xff0c;从而可以更精确地捕捉细节以更…

编程魔法:基于LLM的AI function开发,如何实现高效数据生成?

基于大语言模型&#xff08;LLM&#xff09;的AI function开发&#xff0c;简直就是现代编程界的“魔法棒”&#xff01; 你好&#xff0c;我是三桥君 最近三桥君有个任务&#xff0c;需要造一些测试数据&#xff0c;比如姓名、手机号、银行卡号、邮箱啥的&#xff0c;用来做测…

PV大题--专题突破

写在前面&#xff1a; PV大题考查使用伪代码控制进程之间的同步互斥关系&#xff0c;它需要我们一定的代码分析能力&#xff0c;算法设计能力&#xff0c;有时候会给你一段伪代码让你补全使用信号量控制的操作&#xff0c;请一定不要相信某些人告诉你只要背一个什么模板&#…

新手必知的录屏工具及其使用方法详解

你平常会录屏吗&#xff1f;录屏已经成为了一项非常实用的技能。无论是制作教学视频还是记录游戏精彩瞬间&#xff0c;录屏都可以帮我们记录我们在电脑上看到的精彩瞬间。今天我们就一同来探索在电脑上如何录屏吧。 1.福昕录屏工具 链接&#xff1a;www.foxitsoftware.cn/REC…

太速科技-FMCJ457-基于JESD204B的2路2Gsps AD 2路2Gsps DA FMC子卡

FMCJ457-基于JESD204B的2路2Gsps AD 2路2Gsps DA FMC子卡 一、板卡概述 该子卡是高速AD9172 DAC和AD9689 ADC的FMC板。为客户提供高达2 GHz 的可用模拟带宽以及 JESD204B 接口&#xff0c;以快速地对各种宽带 RF 应用进行原型制作。 AD芯片AD9689&#xff0c;AD9689-2…

Ceph RocksDB 深度调优

介绍 调优 Ceph 可能是一项艰巨的挑战。在 Ceph、RocksDB 和 Linux 内核之间&#xff0c;实际上有数以千计的选项可以进行调整以提高存储性能和效率。由于涉及的复杂性&#xff0c;比较优的配置通常分散在博客文章或邮件列表中&#xff0c;但是往往都没有说明这些设置的实际作…

论文翻译 | LLaMA-Adapter :具有零初始化注意的语言模型的有效微调

摘要 我们提出了一种轻量级的自适应方法&#xff0c;可以有效地将LLaMA微调为指令遵循模型。lama - adapter采用52K自指导演示&#xff0c;在冻结的LLaMA 7B模型上只引入1.2M可学习参数&#xff0c;在8个A100 gpu上进行微调花费不到一个小时。具体来说&#xff0c;我们采用了一…