RequestContextHolder跨线程获取不到request对象,解决方法

一、前言

最近在做一个系统的全局日志拦截记录功能,有一个需要记录的IP地址的信息,我是从HttpServletRequest对象中获取的,但是我发现如果使用线程池以后,记录日志信息会报错,主要是获取不到HttpServletRequest对象。

下面使用代码简单演示一下问题和解决方法:

二、代码演示

  1. 创建一个spring boot项目,项目结构如下:

在这里插入图片描述
2. 导入maven依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
  1. 为了方便测试,创建GlobalWebUtils工具类:
package com.learn.util;

import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

public class GlobalWebUtils {

    /**
     * 获取HttpServletRequest对象
     *
     * @return
     */
    public static HttpServletRequest getRequest() {
        HttpServletRequest request = null;
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes != null) {
            request = ((ServletRequestAttributes) requestAttributes).getRequest();
        }
        return request;
    }
}
  1. 创建HelloController进行测试:
@RestController
public class HelloController {

    @GetMapping("/hello")
    public void hello(String name) {
        
        // 获取HttpServletRequest对象
        HttpServletRequest request = GlobalWebUtils.getRequest();

        if (request != null) {
            String helloName = request.getParameter("name");
            System.out.println("hello," + name);
        } else {
            System.out.println("获取不到request对象");
        }
    }

}

启动项目,访问:http://localhost:8080/hello?name=张三

测试结果:

hello,张三

下面改造一下hello方法,使用多线程的方法执行该代码:

    @GetMapping("/hello")
    public void hello(String name) {
        
        new Thread(() -> {
            // 获取HttpServletRequest对象
            HttpServletRequest request = GlobalWebUtils.getRequest();

            if (request != null) {
                String helloName = request.getParameter("name");
                System.out.println("hello," + name);
            } else {
                System.out.println("获取不到request对象");
            }
        }).start();

    }

再次访问,测试结果:

获取不到request对象

可以看到如果使用多线程的话,就获取不到父线程中的HttpServletRequest对象了。

三、解决方法

解决方法其实很简单,调用一下RequestContextHolder的setRequestAttributes方法就行了,代码如下:

    @GetMapping("/hello")
    public void hello(String name) {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        RequestContextHolder.setRequestAttributes(requestAttributes,true);

        new Thread(() -> {
            // 获取HttpServletRequest对象
            HttpServletRequest request = GlobalWebUtils.getRequest();

            if (request != null) {
                String helloName = request.getParameter("name");
                System.out.println("hello," + name);
            } else {
                System.out.println("获取不到request对象");
            }
        }).start();

    }

再次测试,测试结果:

hello,张三

四、原理

首先看一下setRequestAttributes方法源码:

/**
	 * Bind the given RequestAttributes to the current thread.
	 * @param attributes the RequestAttributes to expose,
	 * or {@code null} to reset the thread-bound context
	 * @param inheritable whether to expose the RequestAttributes as inheritable
	 * for child threads (using an {@link InheritableThreadLocal})
	 */
	public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
		if (attributes == null) {
			resetRequestAttributes();
		}
		else {
			if (inheritable) {
				inheritableRequestAttributesHolder.set(attributes);
				requestAttributesHolder.remove();
			}
			else {
				requestAttributesHolder.set(attributes);
				inheritableRequestAttributesHolder.remove();
			}
		}
	}

其实看到源码就茅塞顿开了,主要看一下requestAttributesHolder和inheritableRequestAttributesHolder的类型,就可以知道是怎么实现的了。

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

父类是ThreadLocal类型:

public class NamedThreadLocal<T> extends ThreadLocal<T>
  • inheritableRequestAttributesHolder:
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
			new NamedInheritableThreadLocal<>("Request context");

父类是InheritableThreadLocal类型:

public class NamedInheritableThreadLocal<T> extends InheritableThreadLocal<T> 

看到这里是不是就很清晰了呢,简单来说就是,调用setRequestAttributes方法以后就把原来放在ThreadLocal对象中的属性放到InheritableThreadLocal对象中了,这就是为什么子线程可以获取到HttpServletRequest 对象的原因。

如果还有不明白的地方,可以参考一下我的另外两篇博客:

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
在使用Feign集成Hystrix时,由于Hystrix的线程池隔离机制,无法直接获取到`RequestContextHolder.currentRequestAttributes()`,因为在新的线程中无法访问当前请求的上下文信息。 解决这个问题的一种常见方法是,通过`HystrixConcurrencyStrategy`来传递上下文信息。下面是一个简单的案例解决方案: 1. 创建一个自定义的`HystrixConcurrencyStrategy`类,继承自`HystrixConcurrencyStrategy`: ```java public class RequestContextHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy { @Override public <T> Callable<T> wrapCallable(Callable<T> callable) { HttpServletRequest request = RequestContextHolder.getRequestAttributes(); // 通过自定义的CallableWrapper将请求上下文信息传递到Hystrix的线程中 return new RequestContextCallableWrapper<>(callable, request); } // 在这里重写一些其他需要的方法 } ``` 2. 创建一个自定义的`CallableWrapper`类,用于在包装`Callable`时传递请求上下文信息: ```java public class RequestContextCallableWrapper<T> implements Callable<T> { private final Callable<T> delegate; private final HttpServletRequest request; public RequestContextCallableWrapper(Callable<T> delegate, HttpServletRequest request) { this.delegate = delegate; this.request = request; } @Override public T call() throws Exception { try { RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); return delegate.call(); } finally { RequestContextHolder.resetRequestAttributes(); } } } ``` 3. 在启动类中,将自定义的`HystrixConcurrencyStrategy`注册到Hystrix中: ```java @Configuration public class HystrixConfig { @PostConstruct public void init() { HystrixPlugins.getInstance().registerConcurrencyStrategy(new RequestContextHystrixConcurrencyStrategy()); } } ``` 这样,就可以在使用Feign集成Hystrix时,通过`RequestContextHolder.currentRequestAttributes()`获取到请求上下文信息了。 需要注意的是,上述代码中的`RequestContextHolder`和`ServletRequestAttributes`是Spring Framework的类,如果你使用的是其他框架,请相应地修改代码。同时,该解决方案也适用于其他情况下无法获取到请求上下文信息的场景。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值