RequestContextHolder分析
一、它是什么?
org.springframework.web.context.request.RequestContextHolder 是 Spring Web 模块提供的一个工具类 。它的核心作用是在当前线程(Thread)中绑定和存储当前 HTTP 请求的上下文信息(即 RequestAttributes 对象),并允许你在代码的任何地方(只要在同一线程内)轻松获取到这些信息。
简单来说,它解决了一个问题:如何在一个非控制器(Controller)的普通类、工具类或业务层(Service)方法中,获取到当前 HTTP 请求的 HttpServletRequest 和 HttpServletResponse 对象?
二、为什么需要它?
在标准的 MVC 模式中,HttpServletRequest 和 HttpServletResponse 对象通常只在 Controller 层作为方法参数被直接使用。
java
@RestController
public class UserController {
@GetMapping("/user")
public String getUserInfo(HttpServletRequest request) { // 在这里可以直接获取
String userAgent = request.getHeader("User-Agent");
// ... 业务逻辑
return "info";
}
}
但当你的业务逻辑深入 Service 层、工具类或其他组件时,将这些对象一层层作为参数传递下去,会使代码变得非常臃肿和耦合,违反了设计的简洁性原则。
RequestContextHolder 的妙处就在于:它通过线程绑定的机制,让你可以在任何地方"凭空"获取到当前请求的上下文,而无需显式地传递它们。
三、它是如何工作的?
其核心机制是 ThreadLocal。
- 线程绑定 :当一个 HTTP 请求到达 Spring MVC 的
DispatcherServlet时,在进入控制器之前,Spring 会通过一个过滤器或拦截器,将当前请求的RequestAttributes(它封装了HttpServletRequest,HttpServletResponse等)对象绑定到当前处理该请求的线程(ThreadLocal) 上。 - 随时获取 :在后续的任意业务逻辑、Service 或工具类中,你都可以通过
RequestContextHolder类的方法,从当前线程的 ThreadLocal 变量中取出之前绑定的RequestAttributes对象。 - 及时清理:当请求处理完毕,返回响应之后,Spring 会自动清理当前线程的 ThreadLocal 数据,防止内存泄漏(尤其是在使用线程池的情况下)。
四、怎么使用它?
RequestContextHolder 提供了静态方法,最常用的有:
1. 获取 RequestAttributes
java
// 最常用的方法:获取当前线程绑定的 RequestAttributes。
// 如果当前线程没有绑定请求上下文,则返回 null。
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
// 获取当前线程绑定的 RequestAttributes。
// 如果当前线程没有绑定请求上下文,则抛出 IllegalStateException 异常。
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
通常推荐在明确处于 Web 请求环境的代码中使用 currentRequestAttributes(),因为它能更快地暴露问题(如果没有上下文,说明你的调用时机或位置不对)。
2. 获取 HttpServletRequest 和 HttpServletResponse
获取到 RequestAttributes 后,可以从中取出需要的对象:
java
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) attributes;
HttpServletRequest request = servletRequestAttributes.getRequest();
HttpServletResponse response = servletRequestAttributes.getResponse();
3. 异步请求中的上下文传递
java
@Service
@Slf4j
public class AsyncService {
@Async // 使用 Spring 的异步执行
public CompletableFuture<String> asyncProcess() {
// 获取请求上下文(需要在配置中启用上下文传递)
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
String requestId = request.getHeader("X-Request-ID");
log.info("异步处理,Request-ID: {}", requestId);
}
// 异步业务逻辑
return CompletableFuture.completedFuture("处理完成");
}
}
/**
* 异步配置 - 启用上下文传递
*/
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("Async-");
executor.initialize();
// 包装 Executor 以传递上下文
return new DelegatingContextExecutor(executor);
}
}
/**
* 包装 Executor 传递上下文
*/
public class DelegatingContextExecutor implements Executor {
private final Executor delegate;
public DelegatingContextExecutor(Executor delegate) {
this.delegate = delegate;
}
@Override
public void execute(Runnable command) {
// 捕获当前请求上下文
RequestAttributes context = RequestContextHolder.currentRequestAttributes();
delegate.execute(() -> {
try {
// 在子线程中设置上下文
RequestContextHolder.setRequestAttributes(context);
runnable.run();
RequestContextHolder.setRequestAttributes(context);
command.run();
} finally {
// 清理上下文
RequestContextHolder.resetRequestAttributes();
}
});
}
}
4. 自定义 RequestAttributes
java
/**
* 自定义请求属性
*/
@Component
public class CustomRequestAttributes extends ServletRequestAttributes {
private final Map<String, Object> customAttributes = new ConcurrentHashMap<>();
public CustomRequestAttributes(HttpServletRequest request, HttpServletResponse response) {
super(request, response);
}
@Override
public Object getAttribute(String name, int scope) {
if (scope == RequestAttributes.SCOPE_REQUEST) {
Object value = customAttributes.get(name);
if (value != null) {
return value;
}
}
return super.getAttribute(name, scope);
}
@Override
public void setAttribute(String name, Object value, int scope) {
if (scope == RequestAttributes.SCOPE_REQUEST) {
customAttributes.put(name, value);
} else {
super.setAttribute(name, value, scope);
}
}
@Override
public void removeAttribute(String name, int scope) {
if (scope == RequestAttributes.SCOPE_REQUEST) {
customAttributes.remove(name);
} else {
super.removeAttribute(name, scope);
}
}
}
/**
* 过滤器设置自定义 RequestAttributes
*/
@Component
public class CustomRequestAttributesFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {
// 创建自定义 RequestAttributes
CustomRequestAttributes attributes = new CustomRequestAttributes(request, response);
// 设置一些自定义属性
attributes.setAttribute("requestId", UUID.randomUUID().toString(),
RequestAttributes.SCOPE_REQUEST);
attributes.setAttribute("startTime", System.currentTimeMillis(),
RequestAttributes.SCOPE_REQUEST);
// 设置到 RequestContextHolder
RequestContextHolder.setRequestAttributes(attributes);
try {
filterChain.doFilter(request, response);
} finally {
// 记录请求耗时
Long startTime = (Long) attributes.getAttribute("startTime",
RequestAttributes.SCOPE_REQUEST);
if (startTime != null) {
long costTime = System.currentTimeMillis() - startTime;
logger.info("请求耗时: {}ms", costTime);
}
attributes.requestCompleted();
RequestContextHolder.resetRequestAttributes();
}
}
}
注意:要使用自定义的RequestAttributes一定需要配置相应的配套过滤器
5. 完整的工具类示例
我们通常会将其封装成一个工具类,方便在整个项目中使用。
java
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
public class HttpContextUtil {
/**
* 获取 HttpServletRequest
*/
public static HttpServletRequest getRequest() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return attributes.getRequest();
}
/**
* 获取 HttpServletResponse
*/
public static HttpServletResponse getResponse() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return attributes.getResponse();
}
/**
* 获取请求中的属性值
*/
public static Object getAttribute(String name) {
HttpServletRequest request = getRequest();
return request.getAttribute(name);
}
/**
* 获取请求头中的值
*/
public static String getHeader(String name) {
HttpServletRequest request = getRequest();
return request.getHeader(name);
}
/**
* 获取请求的完整 URL
*/
public static String getRequestURL() {
HttpServletRequest request = getRequest();
return request.getRequestURL().toString();
}
/**
* 获取客户端 IP 地址
*/
public static String getClientIP() {
HttpServletRequest request = getRequest();
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
五、使用场景与示例
-
在 Service 层获取用户信息:
java@Service public class UserServiceImpl implements UserService { @Override public void doBusiness() { HttpServletRequest request = HttpContextUtil.getRequest(); String token = request.getHeader("Authorization"); // ... 根据 token 解析用户信息并进行业务操作 } } -
在自定义注解或 AOP 切面中实现权限校验:
java@Aspect @Component public class PermissionAspect { @Around("@annotation(requiresPermission)") public Object checkPermission(ProceedingJoinPoint joinPoint, RequiresPermission requiresPermission) throws Throwable { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); String userId = (String) request.getSession().getAttribute("userId"); // ... 校验用户权限逻辑 if (!hasPermission(userId, requiresPermission.value())) { throw new RuntimeException("无权限访问"); } return joinPoint.proceed(); } } -
在工具类中记录请求日志:
java@Component public class LogUtils { public void recordAccessLog() { HttpServletRequest request = HttpContextUtil.getRequest(); String ip = HttpContextUtil.getClientIP(); String url = request.getRequestURL().toString(); String method = request.getMethod(); // ... 将日志信息存入数据库或发送到消息队列 } }
六、重要注意事项
- 线程安全性 :
RequestContextHolder本身是线程安全的,因为它基于 ThreadLocal。每个线程操作的都是自己独有的副本,不会相互干扰。 - 环境限制 :它只能在 Spring Web 环境下的 HTTP 请求线程中使用 。如果你在以下场景调用,会抛出
IllegalStateException:- 异步线程(@Async):新开启的异步线程无法获取到原请求线程的上下文。
- 定时任务(@Scheduled):定时任务没有与之关联的 HTTP 请求。
- 普通的 main 方法 或消息监听器(如果监听器不在 Web 请求线程中处理消息)。
- 异步编程中的上下文传递 :如果需要在异步线程中使用请求上下文,必须在启动异步任务之前,将
RequestAttributes对象捕获并传递到新线程中,然后在新线程开始时将其重新绑定。Spring 提供了RequestContextListener和支持DelegatingRequestContext的Executor来帮助实现这一点。
总结
RequestContextHolder 是 Spring Web 生态中一个"幕后英雄"式的工具类,它利用 ThreadLocal 巧妙地将 HTTP 请求上下文贯穿整个请求处理生命周期,极大地增加了代码的灵活性和可维护性,是解耦 Controller 层与下层组件依赖的利器。理解并正确使用它,是成为 Spring 高级开发者的重要一步。