优雅的使用工厂方法_threadlocal作用和使用场景

优雅的使用工厂方法_threadlocal作用和使用场景优雅的使用 ThreadLocal

大家好,又见面了,我是你们的朋友全栈君。

前言

在我们日常 Java Web 开发中难免遇到需要把一个参数层层的传递到最内层,然后中间层根本不需要使用这个参数,或者是仅仅在特定的工具类中使用,这样我们完全没有必要在每一个方法里面都传递这样一个通用的参数。如果有一个办法能够在任何一个类里面想用的时候直接拿来使用就太好了。JavaWeb项目大部分都是基于Tomcat,每次访问都是一个新的线程,这样让我们联想到了ThreadLocal,每一个线程都独享一个ThreadLocal,在接收请求的时候set特定内容,在需要的时候get这个值。下面我们就进入主题。

ThreadLocal

维持线程封闭性的一种更规范的方法就是使用ThreadLocal,这个类能使线程中的某个值与保存的值的对象关联起来。ThreadLocal提供getset等接口或方法,这些方法为每一个使用这个变量的线程都存有一份独立的副本,因此get总是返回由当前线程在调用set时设置的最新值。
ThreadLocal有如下方法:

1public T get({ }
2public void set(value{ }
3public void remove({ }
4protected T initialValue({ }

get()方法是用来获取ThreadLocal在当前线程中保存的变量副本  
set()用来设置当前线程中变量的副本  
remove()用来移除当前线程中变量的副本   
initialValue()是一个protected方法,一般是用来在使用时进行重写的,如果在没有set的时候就调用get,会调用initialValue方法初始化内容。
为了使用的更放心,我们简单的看一下具体的实现:

set方法

1public void set(value{
    
    
2        Thread t = Thread.currentThread();
3        ThreadLocalMap map = getMap(t);
4        if (map != null)
5            map.set(thisvalue);
6        else
7            createMap(t, value);
8    }

set方法会获取当前的线程,通过当前线程获取ThreadLocalMap对象。然后把需要存储的值放到这个map里面。如果没有就调用createMap创建对象。

getMap方法

1 ThreadLocalMap getMap(Thread t) {
    
    
2    return t.threadLocals;
3 }

getMap方法直接返回当前ThreadthreadLocals变量,这样说明了之所以说ThreadLocal线程局部变量就是因为它只是通过ThreadLocal变量存在了Thread本身而已。

createMap方法

1void createMap(Thread t, T firstValue) {
    
    
2    t.threadLocals = new ThreadLocalMap(this, firstValue);
3}

set的时候如果不存在threadLocals,直接创建对象。由上看出,放入mapkey是当前的ThreadLocalvalue是需要存放的内容,所以我们设置属性的时候需要注意存放和获取的是一个ThreadLocal

get方法

 1public T get() {
    
    
2        Thread t = Thread.currentThread();
3        ThreadLocalMap map = getMap(t);
4        if (map != null) {
5            ThreadLocalMap.Entry e = map.getEntry(this);
6            if (e != null)
7                return (T)e.value;
8        }
9        return setInitialValue();
10    }

get方法就比较简单,获取当前线程,尝试获取当前线程里面的threadLocals,如果没有获取到就调用setInitialValue方法,setInitialValue基本和set是一样的,就不累累述了。

场景

本文应用ThreadLocal的场景:在调用API接口的时候传递了一些公共参数,这些公共参数携带了一些设备信息,服务端接口根据不同的信息组装不同的格式数据返回给客户端。假定服务器端需要通过设备类型(device)来下发下载地址,当然接口也有同样的其他逻辑,我们只要在返回数据的时候判断好是什么类型的客户端就好了。如下:

场景一

请求

1GET api/users?device=android

返回

1 {
    
    
2  user : {  
3  },
4  link : "https://play.google.com/store/apps/details?id=***"
5 }

场景二

请求

1GET api/users?device=ios

返回

1 {
    
    
2  user : { 
3  },
4  link : "https://itunes.apple.com/us/app/**"
5 }

实现

首先准备一个BaseSigntureRequest类用来存放公共参数

 1public class BaseSignatureRequest { 
     
2    private String device;
3
4    public String getDevice() {
5        return device;
6    }
7
8    public void setDevice(String device) {
9        this.device = device;
10    }
11}

然后准备一个staticThreadLocal类用来存放ThreadLocal,以便存储和获取时候的ThreadLocal一致。

1public class ThreadLocalCache { 
     
2    public static ThreadLocal<BaseSignatureRequest> 
3     baseSignatureRequestThreadLocal = new ThreadLocal<>();
4}

然后编写一个Interceptor,在请求的时候获取device参数,存入当前线程的ThreadLocal中。这里需要注意的是,重写了afterCompletion方法,当请求结束的时候把ThreadLocal remove,移除不必须要键值对。

 1public class ParameterInterceptor implements HandlerInterceptor {
    
    
2    @Override
3    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
4                             Object handler)
 throws Exception 
{
5        String device = request.getParameter("device");
6        BaseSignatureRequest baseSignatureRequest = new BaseSignatureRequest();
7        baseSignatureRequest.setDevice(device);
8        ThreadLocalCache.baseSignatureRequestThreadLocal.set(baseSignatureRequest);
9        return true;
10    }
11
12    @Override
13    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
14                                Object handler, Exception ex)
 throws Exception 
{
15        ThreadLocalCache.baseSignatureRequestThreadLocal.remove();
16    }
17
18    @Override
19    public void postHandle(HttpServletRequest httpServletRequest,
20                           HttpServletResponse httpServletResponse, 
21                           Object o, ModelAndView modelAndView)
 throws Exception 
{
22
23    }
24}

当然需要在spring里面配置interceptor

1    <mvc:interceptors>
2        <mvc:interceptor>
3            <mvc:mapping path="/api/**"/>
4            <bean class="life.majiang.ParameterInterceptor"></bean>
5        </mvc:interceptor>
6    </mvc:interceptors>

最后在Converter里面转换实体的时候直接使用即可,这样就大功告成了。

 1public class UserConverter {
    
    
2    public static ResultDO toDO(User user{
3        ResultDO resultDO = new ResultDO();
4        resultDO.setUser(user);
5        BaseSignatureRequest baseSignatureRequest = ThreadLocalCache.baseSignatureRequestThreadLocal.get();
6        String device = baseSignatureRequest.getDevice();
7        if (StringUtils.equals(device, "ios")) {
8            resultDO.setLink("https://itunes.apple.com/us/app/**");
9        } else {
10            resultDO.setLink("https://play.google.com/store/apps/details?id=***");
11        }
12        return resultDO;
13    }

总结

这种机制很方便,因为他避免了在调用每一个方法时都要传递执行上下文信息,合理的使用ThreadLocal可以起到事倍功半的效果,但是需要避免滥用,例如将所有的全局变量作为ThreadLocal对象,ThreadLocal类似全局变量,他能降低代码的可重用性,并在类之间引入隐含的耦合性,所以再使用前需要格外小心。



原文发布时间为:2018-09-07
本文作者:码匠笔记
本文来自云栖社区合作伙伴“IT先森养成记”,了解相关信息可以关注“IT先森养成记”。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/107294.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 快速阶乘算法python_【最全】阶乘算法!(python和C语言)

    快速阶乘算法python_【最全】阶乘算法!(python和C语言)阶乘的计算叁岁学编程:用最简单的大白话理解编程,欢迎大家关注,留言,提问,希望和大家一起提升!文章目录阶乘的计算阶乘定义:解析方法一:for循环计算方法二:定义for循环的函数计算方法三:定义递归函数计算小知识:C语言代码方法一:for函数方法二:递归函数总结:阶乘定义:阶乘指从1乘以2乘以3乘以4一直乘到所要求的数。例如所要求的数是4,则阶乘式是1×2×3×4,得到的积是24。24就是4的阶乘。…

    2022年7月24日
    9
  • Apache 配置与应用

    Apache 配置与应用目录引言一、Apache连接保持二、Apache的访问控制1.客户机地址限制2.用户授权控制2.1创建用户认证数据文件2.2添加用户授权配置2.3验证用户访问授权三、Apache日志分割1.Apache自带rotatelogs分割工具2.使用第三方工具cronolog分割四、AWStats日志分析1.部署AWStats分析系统2.访问AWStats分析系统总结引言ApacheHTTPServer之所以受到众多企业的青睐,得益于其源代码开源,跨平台、功能

    2022年7月14日
    16
  • flask 数据库迁移_数据库迁移方案

    flask 数据库迁移_数据库迁移方案    在开发的过程中,需要修改数据库的模型,而且需要在修改之后更新数据库,最直接就是删除旧表,但是会丢失数据。所有最好的方式就是数据库迁移。它可以追踪数据库模型的变化,然后把变动应用到数据库中。    在flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到flask-Script中,所有的操作通过命令就能完成。Flask-Migrate提供了一个MigrateComma…

    2022年10月8日
    3
  • 电脑爱好者GHOSTWIN7纯净版v2.0

    电脑爱好者GHOSTWIN7纯净版v2.0系统特点:全自动无人值守安装,采用万能GHOST技术,安装系统过程只需5-8分钟,适合新旧各种机型。集成常见硬件驱动,智能识别+预解压技术,绝大多数硬件可以快速自动安装相应的驱动。VC++2005|2008|2010|2012|2013系统使用系统总裁论坛最新封装工具和IT天空论坛(稳定版)最新驱动包制作而成。我的文档收藏夹虚拟内存智能转移精简列表:示例文件Wi…

    2022年5月1日
    44
  • Win10+Ubuntu18.04双系统安装教程「建议收藏」

    Win10+Ubuntu18.04双系统安装教程「建议收藏」一.说在前头不同的配置安装方法不同,我也是小白第一次安,也是看了无数个教程不断重安了无数次才成功的,所以我的教程不一定适合你的配置,但你可以耐心的按照我的思路尝试,如果你有更好的想法,欢迎指出。我的配置如下:神舟笔记本,双硬盘(128固态+1t机械),bios模式为uefi(按Win+R打开运行,输入msinfo32,回车查看系统信息。在BIOS模式中如果显示“传统”,表示系统启动方式为…

    2022年7月24日
    6
  • layer 弹出层传递参数

    layer 弹出层传递参数layer弹出层轻量好用,一直喜欢用,但是却没有弹出层传参的接口,迫于无奈只能Url地址传参,总所周知,这个可是限制大小的,百度一番,看看大神们怎么处理,结果就感觉所有回答都是Ctrl+C加Ctrl+V,全是地址栏传参,页面缓存、cookie、localstorage,本着不甘心的原则,就想为什么不能在open的时候把参数一起作为属性传递了,强行看了layer源码(扒光慢慢看),于是乎…

    2022年7月13日
    12

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号