RequestContextHolder 与 HttpServletRequest 的联系

1. 什么是 RequestContextHolder?

  • RequestContextHolderSpring 框架 提供的一个工具类,用于在当前线程中存储和获取与请求相关的上下文信息。
  • 它是基于 ThreadLocal 实现的,能够保证每个线程独立存储和访问请求信息。

与 HttpServletRequest 的关系:

HttpServletRequest

  • 是标准的 Servlet API 提供的类,用于表示一个 HTTP 请求。

  • 在 Controller 方法中,我们通常通过参数注入来获取它:

    java 复制代码
    @GetMapping("/example")
    public String example(HttpServletRequest request) {
        String param = request.getParameter("name");
        return "Parameter: " + param;
    }

RequestContextHolder

  • 是 Spring 对请求上下文的封装。
  • 它的核心是通过 RequestAttributes (Spring 自定义的接口)来间接操作 HttpServletRequest,从而获取请求信息。
  • 它的最大优势是:在 Service 层、过滤器、拦截器 等不能直接注入 HttpServletRequest 的地方,也能获取请求信息。

2. RequestContextHolder 与 HttpServletRequest 的联系

核心联系

  • RequestContextHolder 不会直接持有 HttpServletRequest,但它持有与请求相关的上下文信息。
  • 这个上下文信息是通过 RequestAttributes 实现的,而 ServletRequestAttributes 是它的一个实现类,封装了 HttpServletRequestHttpServletResponse

示意图

java 复制代码
HttpServletRequest  
    ↓  
ServletRequestAttributes(封装 HttpServletRequest)  
    ↓  
RequestContextHolder(通过 ThreadLocal 保存 ServletRequestAttributes)
  • 当 Spring 处理请求时,它会将 HttpServletRequest 封装成 ServletRequestAttributes,然后绑定到当前线程的 ThreadLocal 中。
  • RequestContextHolder 就是通过 ThreadLocal 拿到 ServletRequestAttributes,再从中获取 HttpServletRequest

3. RequestContextHolder 的工作原理

绑定请求上下文

Spring 在处理 HTTP 请求时,会自动把 HttpServletRequest 封装成 ServletRequestAttributes 并绑定到线程中:

java 复制代码
ServletRequestAttributes attributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(attributes);

获取请求对象

我们可以通过 RequestContextHolder 拿到请求上下文,进一步获取 HttpServletRequest

java 复制代码
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();

总结RequestContextHolder 是一个中间桥梁,它通过 ServletRequestAttributes 间接地连接到 HttpServletRequest


4. RequestContextHolder 提供的主要方法

获取请求上下文

**getRequestAttributes():**获取当前线程保存的请求上下文。

示例

java 复制代码
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
if (attributes instanceof ServletRequestAttributes) {
    HttpServletRequest request = ((ServletRequestAttributes) attributes).getRequest();
    System.out.println("请求参数:" + request.getParameter("name"));
}
  • 返回类型是 RequestAttributes,需要强转为 ServletRequestAttributes 才能拿到 HttpServletRequest

Spring 设计 RequestAttributes 作为一个 通用接口,这样可以支持不同类型的请求上下文,而不仅仅是 HTTP 请求。

  • RequestAttributes:定义了请求上下文的通用方法,不依赖于特定类型的请求。
  • ServletRequestAttributesRequestAttributes 的一个具体实现,专门封装 Servlet 环境 下的 HttpServletRequestHttpServletResponse

为什么需要强制转换?

因为 RequestAttributes 接口 是通用的,它不知道自己具体是什么请求类型(例如 Servlet 请求、Portlet 请求等)。

  • RequestAttributes 只提供了通用方法,比如存储和获取请求属性。
  • ServletRequestAttributes 提供了专门的方法,比如 getRequest()getResponse(),用于获取 HttpServletRequestHttpServletResponse

currentRequestAttributes()

  • getRequestAttributes() 类似,但如果没有请求上下文,它会抛出异常。

RequestContextHolder 提供的方法

方法 作用
getRequestAttributes() 获取当前线程绑定的 RequestAttributes,如果没有返回 null
currentRequestAttributes() 获取当前线程的 RequestAttributes,如果没有会抛出异常。
setRequestAttributes(RequestAttributes) 手动将 RequestAttributes 绑定到当前线程。适用于手动设置上下文的场景。
resetRequestAttributes() 清除当前线程绑定的 RequestAttributes,防止内存泄漏。
setRequestAttributes(RequestAttributes, boolean inheritable) 支持将请求上下文传递给子线程。inheritable = true 表示上下文可继承。

5. 使用场景及代码示例

场景 1:在 Service 层获取 HttpServletRequest

通常情况下,Service 层不能直接访问 HttpServletRequest,但可以通过 RequestContextHolder 获取:

java 复制代码
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

public class RequestUtil {

    public static HttpServletRequest getCurrentRequest() {
        ServletRequestAttributes attributes =
            (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes != null ? attributes.getRequest() : null;
    }
}

使用

java 复制代码
public void printRequestParam() {
    HttpServletRequest request = RequestUtil.getCurrentRequest();
    if (request != null) {
        String name = request.getParameter("name");
        System.out.println("请求参数 name:" + name);
    } else {
        System.out.println("无法获取 HttpServletRequest");
    }
}

场景 2:在异步线程中传递请求上下文

默认情况下,Spring 的请求上下文不会自动传递给新线程。需要手动设置:

java 复制代码
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

public class AsyncRequestExample {

    public void processInAsyncThread() {
        // 获取当前请求上下文
        RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();

        new Thread(() -> {
            try {
                // 绑定请求上下文到新线程
                RequestContextHolder.setRequestAttributes(attributes, true);
                
                // 获取 HttpServletRequest
                HttpServletRequest request =
                    ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
                System.out.println("异步线程中获取请求参数:" + request.getParameter("name"));
            } finally {
                // 清理请求上下文,防止内存泄漏
                RequestContextHolder.resetRequestAttributes();
            }
        }).start();
    }
}

场景 3:过滤器或拦截器中设置请求上下文

在自定义的过滤器中手动设置请求上下文:

java 复制代码
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

public class CustomFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        // 手动设置上下文
        RequestContextHolder.setRequestAttributes(new ServletRequestAttributes((HttpServletRequest) request));
        
        try {
            chain.doFilter(request, response);
        } finally {
            // 清理上下文,防止内存泄漏
            RequestContextHolder.resetRequestAttributes();
        }
    }
}

6. RequestContextHolder 的注意事项

  • 必须在请求线程中使用

    • RequestContextHolder 依赖于 ThreadLocal,只能在处理请求的线程中使用。
    • 如果在非 Web 环境或没有 HTTP 请求的线程中调用,会返回 null 或抛出异常。
  • 异步线程问题

    • 异步线程无法自动继承父线程的请求上下文,必须手动通过 setRequestAttributes 传递。
  • 内存泄漏风险

    • 在使用线程池时,如果不清理请求上下文(resetRequestAttributes),可能会导致内存泄漏。
相关推荐
eternal__day4 分钟前
数据结十大排序之(选排,希尔,插排,堆排)
java·数据结构·算法·推荐算法
我焦虑的编程日记5 分钟前
【期末复习】JavaEE(上)
java·java-ee
o不ok!8 分钟前
java中File类
java·开发语言
小小小小关同学11 分钟前
【并发容器】ConcurrentLinkedQueue:优雅地实现非阻塞式线程安全队列
java·开发语言·安全
熬夜的猪21 分钟前
Redis 最佳实践
java·redis·后端
Qzer_40723 分钟前
在JVM(Java虚拟机)中,PC寄存器(Program Counter Register)扮演着至关重要的角色。
java·开发语言·jvm
yuanbenshidiaos26 分钟前
多线程----互斥
java·jvm·redis
岁岁岁平安29 分钟前
spring学习(spring-bean实例化(静态工厂))
java·学习·spring·bean·静态工厂模式·factory_method
老马啸西风33 分钟前
分布式链路追踪-02-Dapper 论文介绍
java
涡能增压发动积35 分钟前
Java不能搞大模型应用开发?SpringAI拯救你
java·人工智能·后端