ThreadLocal解决多语言标识在多线程中无法传递的问题

问题出现

项目中进行国际化适配,系统需要支持多语言转化。系统根据请求头中的header的值判使用哪种语言。
但是在新线程中以异步操作是拿不到request对象的。

// 注意:实例伪代码,不符合编码规范!
 @RequestMapping(value = "/queryInfo", method = RequestMethod.POST)
    public Result<?> batchIssueInvoice(@RequestBody UserInfo userInfo) {
    	// 主线程中能够拿到request对象,request不为null
    //HttpServletRequest  request= ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    	String l=getLanguage();
        // 新启动一个线程,在子线程中拿不到request对象
        new Thread(() -> {
        		// 此时,request==null
      			methotest1(userInfo);
      			methotest2(userInfo);
                 // request为null,此时无法拿到header中的值,系统无法正常判断语言类型。会默认返回中文!不符合其他语言环境的要求!
            }).start();
        
    }
    // 获取语言环境的方法
    public static String getLanguage(){
        HttpServletRequest request;
        request= ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return request.getHeader("language")==null?"cn":request.getHeader("language");
    }
    
    public void methotest1(UserInfo userInfo){
    	getLanguage()
    	//此处需要获取语言标识进行其他操作
    }
    
    public void methotest2(UserInfo userInfo){
    	getLanguage()
    	//此处需要获取语言标识进行其他操作
    }

解决方案一

将request中的对象作为参数传入子线程中

// 注意:实例伪代码,不符合编码规范!
 @RequestMapping(value = "/queryInfo", method = RequestMethod.POST)
    public Result<?> batchIssueInvoice(@RequestBody UserInfo userInfo) {
    	// 主线程中能够拿到request对象,request不为null
   // HttpServletRequest  request= ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
   		String l=getLanguage();
        // 新启动一个线程,在子线程中拿不到request对象
        new Thread(() -> {
        		// 此时,request==null
      			methotest1(userInfo,l);
      			methotest2(userInfo,l);
                 // request为null,此时无法拿到header中的值,系统无法正常判断语言类型。会默认返回中文!不符合其他语言环境的要求!
            }).start();
        
    }
    // 获取语言环境的方法
    public static String getLanguage(){
        HttpServletRequest request;
        request= ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return request.getHeader("language")==null?"cn":request.getHeader("language");
    }
    
    public void methotest1(UserInfo userInfo,String language){
    	//此处需要获取语言标识进行其他操作
    }
    
    public void methotest2(UserInfo userInfo,String language){
    	//此处需要获取语言标识进行其他操作
    }

这样虽然能够解决问题,但是在实际的场景中,很多方法中会用到语言标识,如果按照这种方式进行改造需要改造所有这些方法,工作量非常大,并且比较low。

解决方案二

在开启线程之前,将request的信息设置为子线程共享

       // 在开启线程之前,将request的信息设置为子线程共享
        ServletRequestAttributes ser = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
		RequestContextHolder.setRequestAttributes(ser, true);
// 注意:实例伪代码,不符合编码规范!
 @RequestMapping(value = "/queryInfo", method = RequestMethod.POST)
    public Result<?> batchIssueInvoice(@RequestBody UserInfo userInfo) {
    	// 主线程中能够拿到request对象,request不为null
    	String l=getLanguage();
        // 在开启线程之前,将request的信息设置为子线程共享
        ServletRequestAttributes ser = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
		RequestContextHolder.setRequestAttributes(ser, true);
        new Thread(() -> {
        		// 此时,request==null
      			methotest1(userInfo);
      			methotest2(userInfo);
                 // request为null,此时无法拿到header中的值,系统无法正常判断语言类型。会默认返回中文!不符合其他语言环境的要求!
            }).start();
        
    }
    // 获取语言环境的方法
    public static String getLanguage(){
        HttpServletRequest request;
        request= ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return request.getHeader("language")==null?"cn":request.getHeader("language");
    }
    
    public void methotest1(UserInfo userInfo){
    	getLanguage()
    	//此处需要获取语言标识进行其他操作
    }
    
    public void methotest2(UserInfo userInfo){
    	getLanguage()
    	//此处需要获取语言标识进行其他操作
    }

这种方式当主线程没有结束的时候子线程确实可以拿到request对象。但是问题又来了,系统中有一些异步操作,主线程是优先结束于异步线程的。这种解决方案当主线程结束后,异步线程同样会出现拿不到request对象的情况。

解决方案三

用全局静态变量缓存request中所需要的数据
用拦截器拦截请求后,将request中需要的数据缓存到全局静态变量中。需要的时候从静态变量中获取,但是静态两边会出现线程安全问题!

主角登场

既然想到了静态变量的线程安全问题,当然要用Thread Local来解决这个问题!
首先我们再来回忆一下Thread Local的相关知识!

ThreadLocal是一个内部线程存储类,可以在指定线程内存储数据,并且存储的数据只能被指定线程所访问。所以ThreadLocal是线程安全的。

Thread Local的使用其实很简单

public static final ThreadLocal<T> threadLocal=new ThreadLocal<>();
threadLocal.set();
threadLocal.get();
// 源码
 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
 }
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
}

   private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
 }
    

我们可以看出Thread Local的底层实现使用了一个ThreadLocalMap,当我们使用set方法时实际上可以看作将当前线程作为Key,我们存入的值作为Value存入ThreadLocalMap中的,当我们get的时候也是从ThreadLocalMap中取值。

解决问题

了解了ThreadLocal的特性之后,采用ThreadLocal缓存Request中所需要的值。

public static final ThreadLocal<String> threadLocal=new ThreadLocal<>();
public static String getLanguage(){
        HttpServletRequest request;
        try {
            request= ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        }catch (Exception e){
            return threadLocal.get()==null?"cn":threadLocal.get();
        }
        if (null==request||null==request.getHeader("language")){
            return threadLocal.get()==null?"cn":threadLocal.get();
        }
        return request.getHeader("language");

    }

// 注意:实例伪代码,不符合编码规范!
 @RequestMapping(value = "/queryInfo", method = RequestMethod.POST)
    public Result<?> batchIssueInvoice(@RequestBody UserInfo userInfo) {
    	// 主线程中能够拿到request对象,request不为null
    	String l=getLanguage();
        new Thread(() -> {
        		// 将request的信息存入子线程
        		threadLocal.set(l)
      			methotest1(userInfo);
      			methotest2(userInfo);
                 // request为null,此时无法拿到header中的值,系统无法正常判断语言类型。会默认返回中文!不符合其他语言环境的要求!
            }).start();
        
    }

这样在子线程无论什么时候需要获取request中的信息都可以获取到了,也保证了线程安全性。

nice~~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LLLDa_&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值