其实在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会好很多