异步线程获取request,获取请求头数据和参数被清空问题处理

本文讲述了在Tomcat中request和response的复用机制,以及在异步线程中处理request头问题的两种方案:标记为异步请求和复制原线程request。同时提到了使用RequestCopy工具类进行请求头拷贝以避免请求被重置的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

其实在tomcat中,request和response是会被复用的,tomcat会维护一个请求池,每次都会从中拿到request设置参数,然后开始一次请求,然后请求结束响应后,会将request和response重置,然后将其放到请求池中,等待后续的复用.

有时我们在异步线程外面使用RequestContextHolder.currentRequestAttributes();
获取到了当前的请求对象,然后再放到异步线程中
RequestContextHolder.setRequestAttributes(requestAttributes);
然后在异步线程中获取请求HttpServletRequest,然后拿请求头,可能获取的是空的,这是因为在异步线程执行完之前,外面的请求线程已经结束了,然后request就被tomcat重置放入池中了,所以导致request请求头,请求参数被重置了,解决这个问题有两种方案
方案一: 将当前请求标记为异步请求,这样tomcat在回收request的时候,就会等待,等待异步完成

AsyncContext asyncContext = request.startAsync();
     // todo 这里执行你想要操作的事情,完成后记得调用complete
     asyncContext.complete();

// 上面的方案有缺陷,tomcat会一直等待request异步执行完成调用complete方法,
//导致前端请求会一直转圈圈,所以可以用下面的方案,让tomcat再起一个线程执行你的异步方法
// 这里也不太好,用的是tomcat的线程池,如果想用自己定义的线程池,还是不太行
// 所以可以用下面的方案二
AsyncContext asyncContext = request.startAsync();
     asyncContext.start(()->{
         
         // todo 做你要做的事
         asyncContext.complete();
     });

方案二:
拷贝原线程的request,然后再放入异步线程
工具类的代码如下

import org.apache.catalina.connector.RequestFacade;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
public class RequestCopy {

    private RequestCopy() {
        throw new IllegalStateException("Utility class");
    }

    /**
     * 拷贝当前线程中的request
     * 注意:仅拷贝了需要的header,异步调用feign接口时所需要的
     * @return
     */
    public static RequestAttributesCopy copyCurrentThreadRequestAttributes(){
        ServletRequestAttributes requestAttributes = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes());
        HttpServletRequest request = requestAttributes.getRequest();
        return copyToRequestAttributes(request);
    }

    /**
     * 拷贝request中的header
     * 注意:仅拷贝了需要的header,异步调用feign接口时所需要的
     * @param request
     * @return
     */
    public static RequestAttributesCopy copyToRequestAttributes(HttpServletRequest request) {
        return new RequestAttributesCopy(copyToRequest(request));
    }

    /**
     * 拷贝request
     * 注意:仅拷贝了需要的header,异步调用feign接口时所需要的
     * @param request
     * @return
     */
    public static HttpServletRequestCopy copyToRequest(HttpServletRequest request) {
        Map<String, String> headerMap = new HashMap();
        Enumeration<String> headers = request.getHeaderNames();
        if (headers != null) {
            while (headers.hasMoreElements()) {
                String header = (String) headers.nextElement();
                    headerMap.put(header, request.getHeader(header));
                }
            }
        }
        return new HttpServletRequestCopy(headerMap);
    }

    public static class RequestAttributesCopy extends ServletRequestAttributes {

        public RequestAttributesCopy(HttpServletRequestCopy request) {
            super(request);
        }
    }

    public static class HttpServletRequestCopy extends RequestFacade {

// 自己定义的请求头保存,看自己的需求来,如果还想获取参数,那就加个参数保存就好
        private final Map<String, String> header;

        public HttpServletRequestCopy(Map<String, String> header) {
            super(null);
            if (header == null) {
                header = Collections.emptyMap();
            }
            this.header = header;
        }

// 这里重写你需要获取的参数,我这边就只用获取请求头,所以就重写了请求头的获取方法
        @Override
        public String getHeader(String name) {
            return header.get(name);
        }

        @Override
        public Enumeration<String> getHeaderNames() {
            return Collections.enumeration(header.keySet());
        }
    }
}

使用如下

// 异步导出,这里是传递request到异步线程中,传递token需要(需要拷贝一份,不然请求结束了,原来的request会被清空)
        RequestCopy.RequestAttributesCopy requestAttributesCopy = RequestCopy.copyCurrentThreadRequestAttributes();
        new Thread(()->{
            RequestContextHolder.setRequestAttributes(requestAttributesCopy);
            // todo 做你想做的事
        }).start();

2023-12-12更新: 替换拷贝的request继承类,原request对象org.apache.catalina.connector.Request里面东西太多了,实例化后占用内存多,相当于一个重量级对象,替换成org.apache.catalina.connector.RequestFacade会好很多

当在Spring MVC应用中尝试通过`RequestContextHolder.getRequestAttributes()`获取当前请求的相关信息,如果返回值为`null`,这通常意味着请求上下文尚未设置或已经被移除。这种情况可能出现在以下几个场景: 1. **在非HTTP请求上下文**:如果你在一个非Web环境中(如服务端应用程序、批处理任务等),或者是在异步处理(比如消息队列消费)时,`RequestContextHolder`可能为空。 2. **异常处理或拦截器之前**:在控制器方法调用之前,如果没有正确地设置`RequestContextHolder`,它也可能为`null`。 3. **手动清除**:有时开发者可能会故意清空请求上下文,例如为了测试目的或在特定阶段。 针对这种情况,你可以采取以下措施处理: - **检查环境**:确认是否处在正确的HTTP请求处理流程内,如果不是,需要创建一个新的`ServletRequestAttributes`实例,并将其绑定到`RequestContextHolder`。 - **添加适当的初始化**:在进入HTTP处理链路的地方(如`@ControllerAdvice`或全局过滤器)确保先设置好`RequestContextHolder`。 - **使用ThreadLocal备份**:如果确信不是跨线程问题,可以考虑使用`ThreadLocal`存储请求属性。 ```java @Autowired private HttpServletRequest request; public void handleRequest() { if (request == null) { RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); } // 现在可以从RequestContextHolder获取请求属性 } ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值