17| 登录拦截器开发和ThreadLocal原理 小滴课堂讲师 2024年08月05日 预计阅读 10 分钟 原文 #### 登录拦截器实现 登录拦截器:解密JWT,传递登录⽤户信息(attribute传递/threadLocal传递) 公共方法增加错误数据响应,从拦截器中之间返出去: ```java /** * 响应json数据给前端 */ public static void sendJsonMessage(HttpServletResponse response, Object obj) { ObjectMapper objectMapper = new ObjectMapper(); response.setContentType("application/json; charset=utf-8"); try (PrintWriter writer = response.getWriter()) { writer.print(objectMapper.writeValueAsString(obj)); response.flushBuffer(); } catch (IOException e) { log.error("响应json数据给前端异常", e); } } ``` 拦截器出去解析及信息传递: ```java /** * 登录拦截器 */ @Slf4j @Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { try { String accessToken = request.getHeader("token"); if (accessToken == null) { accessToken = request.getParameter("token"); } if (StringUtils.isNotBlank(accessToken)) { Claims claims = JWTUtil.checkJWT(accessToken); if (claims == null) { //告诉登录过期,新登录 CommonUtil.sendJsonMessage(response, JsonData.buildError("登录过期,请重新登录")); return false; } Long id = Long.valueOf(claims.get("id").toString()); String headImg = (String) claims.get("head_img"); String mail = (String) claims.get("mail"); String name = (String) claims.get("name"); log.info("用户信息:ID:{}, 头像:{},邮箱:{},名称:{}", id, headImg, mail, name); //TODO ⽤户信息传递 return true; } } catch (Exception e) { log.error("拦截器错误", e); } CommonUtil.sendJsonMessage(response, JsonData.buildError("token不存在,请重新登录")); return false; } } ``` #### Threadlocal定义及使用场景 定义:全称thread local variable(`线程局部变量`)功能非常简单,使用场合`主要解决多线程中数据因并发产⽣不⼀致问题`。ThreadLocal为每⼀个线程都提供了`变量的副本`,使得每个线程在某时间访问到的并不是同⼀个对象,这样就隔离了多个线程对数据的数据共享,这样的结果是耗费了内存,但大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。总结起来就是:同个线程共享数据 >注意:`ThreadLocal不能使⽤原⼦类型,只能使⽤Object类型` 核心应用场景: ThreadLocal 用作`每个线程内需要独⽴保存信息`,方便同个线程的其他方法获取该信息的场景。 `每个线程获取到的信息可能都是不⼀样的,前⾯执⾏的⽅法保存了信息后,后续⽅法可以通过ThreadLocal 直接获取到,避免了传参,类似于全局变的概念`,比如用户登录令牌解密后的信息传递(还有用户权限信息、从用户系统获取到的用户名、用户ID) 用户信息传递补全代码: ```java public static ThreadLocal threadLocal = new ThreadLocal<>();// 类的静态变量 // preHandle中⽤户信息传递补全 LoginUser loginUser = new LoginUser(); loginUser.setName(name); loginUser.setHeadImg(headImg); loginUser.setId(id); loginUser.setMail(mail); //通过attribute传递用户信息 //request.setAttribute("loginUser",loginUser); //通过threadLocal传递用户登录信息 threadLocal.set(loginUser); ``` #### ThreadLocal底层源码 - ThreadLocal中的⼀个内部类`ThreadLocalMap`,这个类没有实现map接口,就是⼀个普通的Java类,但是实现的类似map的功能 - 每个数据用Entry保存,其中的Entry继承与`WeakReference`,用⼀个键值对存储,键为ThreadLocal的引用。 - 每个线程持有⼀个ThreadLocalMap对象,每⼀个新的线程Thread都会实例化⼀个ThreadLocalMap并赋值给成员变量threadLocals,使用时若已经存在threadLocals,则直接使用已经存在的对象。 #### ThreadLocal核⼼⾯试题 **ThreadLocal和Synchronized的区别** - 都是为了`解决多线程中相同变量的访问冲突问题` - Synchronized是通过`线程等待`,`牺牲时间`来解决访问冲突 - ThreadLocal是通过`每个线程单独⼀份存储空间`,`牲空间`来解决冲突, - 对⽐Synchronized,`ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值`,线程外则不能访问到想要的值 **为什么ThreadLocal的键是弱引⽤,如果是强引⽤有什么问题?** Java中除了基础的数据类型以外,其它的都为引⽤类型。⽽Java根据其⽣命周期的⻓短将引⽤类型⼜分为强引⽤ 、 软引⽤ 、 弱引⽤ 、 虚引⽤。正常情况下我们平时基本上我们只⽤到强引⽤类型,⽽其他的引⽤类型我们也就在⾯试中,或者平⽇阅读类库或其他框架源码的时候才能⻅到。 1、强引⽤ new了⼀个对象就是强引⽤ Object obj = newObject(); 2、软引⽤的⽣命周期⽐强引⽤短⼀些,通过`SoftReference`类实现,当内存空间⾜够,垃圾回收器就不会回收它; 当JVM认为内存空间不⾜时,就会去试图回收软引⽤指向的对象,也就是说在JVM抛出OutOfMemoryError之前,会去清理软引⽤对象。主要⽤来描述⼀些【有⽤但并不是必需】的对象 使⽤场景:`适合⽤来实现缓存`,内存空间充⾜的时候将数据缓存在内存中,如果空间不⾜了就将其回收掉 3、弱引⽤是通过`WeakReference`类实现的,它的⽣命周期⽐软。引⽤还要短,在GC的时候,不管内存空间⾜不⾜都会回收这个对象 使⽤场景:`⼀个对象只是偶尔使⽤,希望在使⽤时能随时获取`,但也不想影响对该对象的垃圾收集,则可以考虑使⽤弱引⽤来指向该对象。 **ThreadLocal为什么是WeakReference呢?** 如果是强引⽤,即使把ThreadLocal设置为null,但是ThreadLocalMap还持有ThreadLocal的强引⽤,`如果没有⼿动删除,ThreadLocal不会被回收,导致Entry内存泄漏` 如果是弱引⽤引⽤ThreadLocal的对象被回收了,由于ThreadLocalMap持 有ThreadLocal的弱引⽤,即使没有⼿动删除,ThreadLocal也会被回收。`value在下⼀次ThreadLocalMap调⽤set、get、remove的时候会被清除`。 #### 录拦截器路径配置 设置定义的拦截器拦截路径与不拦截路径: ```java /** * 注册拦截器 */ @Configuration public class WebConfig implements WebMvcConfigurer { @Resource private LoginInterceptor loginInterceptor; @Resource private ResponseResultInterceptor responseResultInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(responseResultInterceptor); registry.addInterceptor(loginInterceptor) //拦截的路径 .addPathPatterns("/user/*/**", "/address/*/**", "/cop/*/**") //排查不拦截的路径 .excludePathPatterns( "/user/**/send", "/user/**/captcha", "/user/**/register", "/user/**/login", "/user/**/uploadFile"); } } ```
评论区