0%

spring获取当前request

Spring 中获取当前 HttpServletRequest 的方法详解

在 Spring(尤其是 Spring MVC)应用中,经常需要在非 Controller 层(如 Service、工具类)获取当前请求的 HttpServletRequest 对象(如获取请求头、客户端 IP、参数等)。核心解决方案是利用 RequestContextHolder 工具类,它通过 ThreadLocal 存储当前请求上下文,确保线程安全。从 “原理→使用方式→注意事项→扩展场景” 四个维度,彻底讲透如何安全、高效地获取 HttpServletRequest

核心原理:RequestContextHolder 与 ThreadLocal

RequestContextHolder 是 Spring 提供的请求上下文持有者,其底层通过 ThreadLocal 存储当前线程的请求上下文(RequestAttributes),确保每个线程只能访问自己的请求对象,避免多线程环境下的资源竞争。

1. 关键类与接口关系

graph LR
    A[RequestContextHolder] --> B[ThreadLocal]
    B --> C[ServletRequestAttributes]
    C --> D[HttpServletRequest]
    C --> E[HttpServletResponse]
  • RequestContextHolder:静态工具类,提供 currentRequestAttributes() 方法获取当前线程的 RequestAttributes
  • RequestAttributes:请求属性接口,定义了获取请求 / 会话属性的方法,其实现类 ServletRequestAttributes 封装了 HttpServletRequestHttpServletResponse
  • ThreadLocal:线程局部变量,确保每个线程的 RequestAttributes 独立存储,互不干扰。

2. 上下文存储时机

Spring MVC 在请求进入时(DispatcherServlet 处理请求的初期),会自动将当前 HttpServletRequest 封装为 ServletRequestAttributes,并通过 RequestContextHolder.setRequestAttributes() 存入 ThreadLocal;请求结束后,会清除 ThreadLocal 中的数据,避免内存泄漏。

核心流程简化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Spring MVC 内部处理逻辑(简化版)
public class DispatcherServlet extends FrameworkServlet {
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
// 1. 将请求封装为 ServletRequestAttributes,存入 RequestContextHolder
RequestAttributes attributes = new ServletRequestAttributes(request, response);
RequestContextHolder.setRequestAttributes(attributes);

// 2. 处理请求(调用 Controller、Service 等)
doDispatch(request, response);
} finally {
// 3. 请求结束,清除 ThreadLocal 中的数据
RequestContextHolder.resetRequestAttributes();
}
}
}

获取 HttpServletRequest 的 3 种方式

根据场景不同,可通过 RequestContextHolder 直接获取,或结合 Spring 依赖注入,以下是常用方式的对比与实战。

方式 1:通过 RequestContextHolder 直接获取(通用)

这是最灵活的方式,适用于任何层级(Service、工具类、Controller),无需依赖注入,核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;

public class RequestUtil {
/**
* 工具方法:获取当前 HttpServletRequest
* @return 当前请求的 HttpServletRequest,若不存在则返回 null
*/
public static HttpServletRequest getCurrentRequest() {
try {
// 1. 获取当前线程的 RequestAttributes(Spring 自动存入)
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();

// 2. 强转为 ServletRequestAttributes(封装了 HttpServletRequest)
ServletRequestAttributes servletAttributes = (ServletRequestAttributes) attributes;

// 3. 获取 HttpServletRequest 对象
return servletAttributes.getRequest();
} catch (IllegalStateException e) {
// 异常场景:当前线程无请求上下文(如异步线程、定时任务)
return null;
}
}

/**
* 扩展:获取当前 HttpServletResponse
*/
public static javax.servlet.http.HttpServletResponse getCurrentResponse() {
try {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return attributes.getResponse();
} catch (IllegalStateException e) {
return null;
}
}
}
实战示例:在 Service 中获取请求头
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;

@Service
public class UserService {
public String getUserAgent() {
// 调用工具类获取当前请求
HttpServletRequest request = RequestUtil.getCurrentRequest();
if (request == null) {
return "非 Web 环境(无请求上下文)";
}
// 获取请求头 User-Agent(客户端浏览器/设备信息)
return request.getHeader("User-Agent");
}
}

方式 2:Controller 层依赖注入(推荐)

在 Controller 层,Spring 支持直接通过方法参数成员变量注入 HttpServletRequest,无需手动通过 RequestContextHolder 获取,代码更简洁。

2.1 方法参数注入(最常用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;

@RestController
public class RequestController {
// 直接将 HttpServletRequest 作为方法参数,Spring 自动注入
@GetMapping("/getIp")
public String getClientIp(HttpServletRequest request) {
// 获取客户端 IP 地址
String clientIp = request.getRemoteAddr();
return "客户端 IP:" + clientIp;
}
}
2.2 成员变量注入(@Autowired)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;

@RestController
public class RequestController {
// 成员变量注入 HttpServletRequest(Spring 自动关联当前请求)
@Autowired
private HttpServletRequest request;

@GetMapping("/getHeader")
public String getHeader() {
// 获取自定义请求头 X-Token
return "X-Token:" + request.getHeader("X-Token");
}
}
注意:
  • 成员变量注入的 HttpServletRequest动态代理对象,Spring 会在每次请求时自动绑定当前线程的请求对象,无需担心线程安全;
  • 仅推荐在 Controller 层使用依赖注入,非 Controller 层(如 Service)更适合用 RequestContextHolder(避免依赖 Web 层 API,降低耦合)。

方式 3:通过 RequestContextListener 手动注册(非 Spring MVC 环境)

若项目非 Spring MVC(如纯 Spring 核心项目),Spring 不会自动存储请求上下文,需手动注册 RequestContextListenerweb.xml,确保 RequestContextHolder 能获取到请求对象:

步骤 1:配置 web.xml
1
2
3
4
5
6
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="4.0">
<!-- 注册请求上下文监听器,确保 RequestContextHolder 能获取请求 -->
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
</web-app>
步骤 2:使用 RequestContextHolder 获取请求
1
2
// 此时可正常通过工具类获取请求(与方式1一致)
HttpServletRequest request = RequestUtil.getCurrentRequest();

关键注意事项(避坑指南)

使用 RequestContextHolder 获取 HttpServletRequest 时,需注意以下场景,避免出现 IllegalStateException(无请求上下文)或线程安全问题。

1. 仅在 Web 请求线程中有效

RequestContextHolder 存储的是当前 Web 请求线程的上下文,若在非 Web 线程(如定时任务、异步线程、普通线程池)中调用 currentRequestAttributes(),会抛出异常:

1
java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request...
解决方案:
  • 定时任务 / 普通线程:若需在非 Web 线程使用请求信息,需在 Web 线程中提前获取并传递(如将 userId 作为参数传入,而非在线程中获取 request);
  • 异步线程:需手动传递请求上下文(见下文 “扩展场景”)。

2. 避免内存泄漏

RequestContextHolderThreadLocal 会在请求结束后被 Spring 自动清除(通过 DispatcherServletfinally 块),但需注意:

  • 若自定义线程池复用线程,且在线程中手动调用 RequestContextHolder.setRequestAttributes(),需在任务结束后调用 RequestContextHolder.resetRequestAttributes(),避免线程复用导致上下文错乱;
  • 不要长期持有 HttpServletRequest 对象(如存入静态变量),否则会导致请求对象无法被 GC 回收,引发内存泄漏。

3. 区分请求类型(普通请求 vs 异步请求)

Spring MVC 异步请求(如返回 CallableDeferredResult)会释放 Web 线程,此时异步线程中默认无法获取 RequestContextHolder 的上下文,需通过 AsyncRequestContext 传递:

异步请求中获取请求的解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import javax.servlet.http.HttpServletRequest;

@RestController
public class AsyncController {
@GetMapping("/async")
public DeferredResult<String> asyncRequest(HttpServletRequest request) {
DeferredResult<String> deferredResult = new DeferredResult<>();

// 异步线程处理任务
new Thread(() -> {
try {
// 1. 手动传递请求上下文(关键:将当前请求的 attributes 传入异步线程)
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
RequestContextHolder.setRequestAttributes(attributes);

// 2. 异步线程中获取请求(此时可正常获取)
HttpServletRequest currentRequest = RequestUtil.getCurrentRequest();
String result = "异步线程获取请求路径:" + currentRequest.getRequestURI();

deferredResult.setResult(result);
} finally {
// 3. 异步线程结束,清除上下文(避免内存泄漏)
RequestContextHolder.resetRequestAttributes();
}
}).start();

return deferredResult;
}
}

扩展场景:获取请求的常用信息

通过 HttpServletRequest 可获取请求的核心信息,以下是常见需求的实现:

需求 核心代码 说明
获取客户端 IP request.getRemoteAddr() 仅获取直接客户端 IP(若有反向代理需特殊处理)
获取请求 URL request.getRequestURL().toString() 完整 URL(如 http://localhost:8080/test
获取请求路径 request.getRequestURI() 上下文路径后的路径(如 /test
获取请求方法 request.getMethod() GET/POST/PUT/DELETE 等
获取请求头 request.getHeader("X-Token") 获取自定义请求头(如 Token)
获取 Cookie request.getCookies() 返回 Cookie 数组,需遍历获取指定 Cookie
获取请求参数 request.getParameter("username") 获取单个参数(Query String 或表单)
获取请求参数 Map request.getParameterMap() 获取所有参数的键值对

处理反向代理后的真实 IP

若项目部署在反向代理(如 Nginx)后,request.getRemoteAddr() 会返回代理服务器的 IP,需通过 Nginx 配置传递真实客户端 IP,并在代码中获取:

1. Nginx 配置(传递真实 IP)
1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
server_name example.com;

location / {
proxy_pass http://localhost:8080;
# 传递真实客户端 IP 和请求头
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
2. 代码中获取真实 IP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static String getRealClientIp(HttpServletRequest request) {
// 1. 先从 X-Real-IP 获取(Nginx 传递的真实 IP)
String ip = request.getHeader("X-Real-IP");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
// 2. 从 X-Forwarded-For 获取(多代理场景,第一个为真实 IP)
ip = request.getHeader("X-Forwarded-For");
if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
// 分割多代理 IP(格式:真实IP,代理1IP,代理2IP)
ip = ip.split(",")[0].trim();
}
}
// 3. 若仍未获取到,使用默认方法
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}

总结

获取 Spring 中的 HttpServletRequest 核心是利用 RequestContextHolderThreadLocal 机制,关键要点如下:

  1. 通用方式:通过 RequestContextHolder.currentRequestAttributes() 获取 ServletRequestAttributes,再强转为 HttpServletRequest,适用于所有层级;
  2. Controller 推荐方式:直接通过方法参数或 @Autowired 注入,代码更简洁;
  3. 避坑重点:仅在 Web 请求线程中有效,非 Web 线程需手动传递上下文,避免内存泄漏;
  4. 扩展场景:反向代理环境需特殊处理真实 IP,异步线程需手动传递请求上下文

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10