手撕一个解决ThreadLocal数据跨线程传递的实用工具

大家好,老吕在前面的文章中提到了如何解决不同场景下ThreadLocal数据传递问题,并且给出了阿里开源TTL框架和手写组件的方案。后来老吕还是觉得TTL的方案有点复杂,手写那个组件不够通用,所以就做了改进,使读写数据通用化。

设计比较简单实用,一个类就搞定了,思路还是通过代理Runnable和Callable接口来增加数据中转的能力,我一直认为这个思路是最划算的。本次加强了数据中转的通用能力。

一、主代码解析

package com.dhy.common.lib.taskproxy;
import lombok.extern.slf4j.Slf4j;


import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;


/**
 * https://github.com/lvaolin/dhy-common-lib/tree/main/dhy-common-lib-taskproxy
 *
 * 代理类:增加了ThreadLocal上绑定的数据中转功能
 */
@Slf4j
public class TaskProxy<V> implements Runnable, Callable {


    /**
     * 被代理没有返回值的任务
     */
    private Runnable runnable;
    /**
     * 被代理有返回值的任务
     */
    private Callable<V> callable;
    /**
     * 数据临时中转站
     */
    private Map<String, Object> localData = new HashMap<>();
    /**
     * ThreadLocal 工具类
     */
    private Class threadLocalHolder;




    public TaskProxy(Runnable runnable, Class threadLocalHolder){
        this.runnable = runnable;
        this.threadLocalHolder = threadLocalHolder;
        storeThreadLocal();
    }
    public TaskProxy(Callable callable, Class threadLocalHolder){
        this.callable = callable;
        this.threadLocalHolder = threadLocalHolder;
        storeThreadLocal();
    }


    @Override
    public void run() {
        restoreThreadLocal();
        this.runnable.run();
        clearThreadLocal();
    }


    @Override
    public Object call() throws Exception {
        restoreThreadLocal();
        V v = this.callable.call();
        clearThreadLocal();
        return v;
    }


    private void storeThreadLocal() {
        Method[] methods = threadLocalHolder.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().startsWith("get")) {
                storeField(method);
            }
        }
    }


    private void storeField(Method method) {
        try {
            Object result = method.invoke(null, null);
            localData.put(method.getName(),result);
            log.info(method.getName()+" invoke");
        } catch (Throwable t){
            log.error(t.getMessage(),t);
        }
    }


    private void restoreThreadLocal() {
        Method[] methods = threadLocalHolder.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().startsWith("set")) {
                restoreField(method);
            }
        }


    }


    private void restoreField(Method method) {
        try {
            Object filedValue = localData.get(method.getName().replaceFirst("s", "g"));
            method.invoke(null, filedValue);
            log.info(method.getName()+" invoke");
        } catch (Throwable t){
            log.error(t.getMessage(),t);
        }
    }


    private void clearThreadLocal() {
        Method[] methods = threadLocalHolder.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().startsWith("remove")) {
                try {
                    method.invoke(null, null);
                    log.info(method.getName()+" invoke");
                } catch (Throwable t){
                    log.error(t.getMessage(),t);
                }
            }
        }
    }


}

二、使用说明

//使用 TaskProxy 类对 Runnable、Callable接口进行一个修饰代理即可
    
    Runnable runnable = (Runnable)new TaskProxy(new Runnable() {
                  @Override
                  public void run() {
                      System.out.println("使用代理Runnable:"+ ThreadLocalHolder.getUserId());
                      System.out.println("使用代理Runnable:"+ ThreadLocalHolder.getAppContext().getSessionId());
                  }
              },ThreadLocalHolder.class);
   
    executorService.submit(runnable);          
     
    //-----------------------------------------
    
    Callable callable = (Callable) new DhyTaskProxy<String>(new Callable() {
                @Override
                public String call() throws Exception {
                    System.out.println("使用代理Callable:"+ ThreadLocalHolder.getUserId());
                    System.out.println("使用代理Callable:"+ ThreadLocalHolder.getAppContext());
                    return "ok";
                }
             }, ThreadLocalHolder.class);
             
     executorService.submit(callable);

ThreadLocal工具类如下

/**
 * ThreadLocal工具类
 */
public class ThreadLocalHolder {


    private static ThreadLocal<String> userIdThreadLocal = new ThreadLocal<>();
    private static ThreadLocal<String> traceIdThreadLocal = new ThreadLocal<>();
    private static ThreadLocal<RequestContext> appContextThreadLocal = new ThreadLocal<>();


    public static void setUserId(String userId){
        userIdThreadLocal.set(userId);
    }
    public static void setTraceId(String traceId){
        traceIdThreadLocal.set(traceId);
    }
    public static void setAppContext(RequestContext appContext){
        appContextThreadLocal.set(appContext);
    }


    public static String getUserId(){
        return userIdThreadLocal.get();
    }
    public static String getTraceId(){
        return traceIdThreadLocal.get();
    }


    public static RequestContext getAppContext(){
        return appContextThreadLocal.get();
    }


    public static void removeUserId(){
        userIdThreadLocal.remove();
    }


    public static void removeTraceId(){
        traceIdThreadLocal.remove();
    }


    public static void removeAppContext(){
        appContextThreadLocal.remove();
    }
}

三、注意事项

1、代理的创建时机,每次使用之前要重新创建代理,不能重用,这个在阿里TTL框架中也提到这个了,原理本质上是从上下文中取数据,所以你的上下文变了后要重新生成代理。

2、本组件依靠ThreadLocal对象holder的static类型的get、set、remove方法来获取、设置、销毁数据的,所以你需要按照示例中的ThreadLocalHolder来写自己的ThreadLocalHolder,这是需要注意的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

吕哥架构

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

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

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

打赏作者

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

抵扣说明:

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

余额充值