我的HTTPRequestHolder呢?

今天写需求,调同事的接口,同事的接口里需要从RequestContext拿一些参数用来查询,我一开始还能查到呢,后边自己“优化”了一下代码,哎嗨?RequestContext里咋空了,啥也没了,我的HTTPRequestHolder对象去哪了?

这就开始debug了。

由于之前重写的那部分代码为了提高性能,使用了CompletableFuture异步编程,而我“优化”代码的时候,为了减少查询次数,把调用同事接口的代码写进了之前的CompletableFuture代码中。也就是在这个时候,我的HTTPRequest对象就没了,那他是怎么没的呢?

让我们先看看RequestContextHolder.getRequestAttributes的底层实现:

1
2
3
4
5
6
7
8
@Nullable
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}

关键就在这个requestAttributesHolder对象:

1
2
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");

它是一个 ThreadLocal 对象,当我们在多线程代码中使用它时,HTTPRequestHolder对象是主线程的私有变量,在其他创建的子线程中,ThreadLocal 对象并不会被共享,自然其他线程就拿不到HTTPRequestHolder对象了。

那该怎么解决这个问题呢?

有两种方法:

  1. RequestContext中的参数传递给被创建的子线程中。
    这种办法非常简单,在接口里多加个形参就好了,我本来不太喜欢这种暴力的方式,但我的mentor告诉我:

    在我现在的场景下,我就应该这么做,因为请求上下文只在主线程中才有意义,子线程中要请求上下文干嘛呢?你就应该把要用的参数直接传递给子线程

  2. 将主线程中使用的上下文,复制到被创建的子线程中。
    这是我比较想用的办法,虽然经过mentor的指点,这个办法不太适合,但我还是想探究一下怎么搞:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    @Configuration
    public class XxPoolConfig {
    /**
    * 获取当前系统的CPU 数目
    */
    static int cpuNums = Runtime.getRuntime().availableProcessors();

    /**
    * 默认线程池
    *
    * @return Executor
    */
    @Bean("xxPool")
    public ThreadPoolTaskExecutor threadPool() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    // 核心线程数:线程池创建时候初始化的线程数
    if(cpuNums==1) {
    executor.setCorePoolSize(5);
    }else {
    executor.setCorePoolSize(10);
    }
    // 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
    executor.setMaxPoolSize(cpuNums * 5);
    //队列中最大的数目
    executor.setQueueCapacity(16);
    //线程名称前缀
    executor.setThreadNamePrefix("CalenderThreadPool_");
    //rejection-policy:当pool已经达到max size的时候,如何处理新任务
    //CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
    //对拒绝task的处理策略
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    //线程空闲后的最大存活时间
    executor.setKeepAliveSeconds(60);
    // 设置线程装饰器
    executor.setTaskDecorator(new ContextDecorator());
    //加载
    executor.initialize();
    return executor;
    }
    }

    关键就在:executor.setTaskDecorator(new ContextDecorator());,给线程设置一个装饰器,在装饰器的具体实现类中进行复制:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class ContextDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
    // 获取主线程中的上下文
    RequestAttributes context = RequestContextHolder.currentRequestAttributes();
    return () -> {
    try {
    // 将上下文复制到新的线程中
    RequestContextHolder.setRequestAttributes(context, true);
    runnable.run();
    } finally {
    RequestContextHolder.resetRequestAttributes();
    }
    };
    }
    }

下篇文章具体研究一下ThreadLocal。