Spring MVC 提供了许多高级抽象(如 @RequestParam
, @PathVariable
, @RequestBody
, Model
, ResponseEntity
等)来简化开发,避免直接操作底层的 Servlet API,但在某些特定场景下,可能仍然需要直接访问 HttpServletRequest
, HttpServletResponse
, HttpSession
等对象。
Spring MVC 提供了非常方便的方式来实现这一点,主要是通过方法参数注入。
方法参数注入 (推荐方式)
这是最常用、最推荐、也是最简洁的方式。只需要在Controller 处理方法(Handler Method)的参数列表中声明需要的 Servlet API 对象类型,Spring MVC 在调用该方法时会自动将当前请求对应的实例注入进来。
支持注入的主要 Servlet API 类型包括:
javax.servlet.ServletRequest
/javax.servlet.http.HttpServletRequest
javax.servlet.ServletResponse
/javax.servlet.http.HttpServletResponse
javax.servlet.http.HttpSession
java.security.Principal
(用于获取当前认证的用户信息)java.util.Locale
(当前请求的区域设置)java.util.TimeZone
(从 Java 8 / Servlet 3.1 开始) /java.time.ZoneId
(Spring 5)java.io.InputStream
/javax.servlet.ServletInputStream
(用于读取请求体)java.io.OutputStream
/javax.servlet.ServletOutputStream
(用于写入响应体)java.io.Reader
(用于读取请求体)java.io.Writer
(用于写入响应体)org.springframework.web.context.request.WebRequest
/org.springframework.web.context.request.NativeWebRequest
(Spring 提供的更丰富的请求封装,可以访问请求和会话属性,但与 Servlet API 解耦)org.springframework.http.HttpMethod
(当前请求的 HTTP 方法)
示例代码:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.Principal;
import java.util.Locale;
@Controller
public class ServletApiController {
@GetMapping("/servlet-access")
@ResponseBody // 使用 @ResponseBody 简化,直接返回字符串;也可以返回视图名或 ModelAndView
public String handleRequest(
HttpServletRequest request, // 注入 HttpServletRequest
HttpServletResponse response, // 注入 HttpServletResponse
HttpSession session, // 注入 HttpSession
Principal principal, // 注入 Principal (如果配置了安全认证)
Locale locale) { // 注入 Locale
// 1. 使用 HttpServletRequest
String remoteAddr = request.getRemoteAddr();
String userAgent = request.getHeader("User-Agent");
String queryParam = request.getParameter("info"); // 不推荐,优先用 @RequestParam
// 2. 使用 HttpServletResponse
// 可以设置响应头、状态码等,但如果方法有返回值且使用了 @ResponseBody 或 ResponseEntity,
// Spring 会接管大部分响应处理,直接操作 response 可能导致冲突或意外行为。
// 更适合在需要直接写入响应流或精细控制时使用(通常配合 void 返回类型)。
// response.setHeader("X-Custom-Info", "From Servlet API");
// response.setStatus(HttpServletResponse.SC_ACCEPTED);
// 3. 使用 HttpSession
Integer visitCount = (Integer) session.getAttribute("visitCount");
if (visitCount == null) {
visitCount = 1;
} else {
visitCount++;
}
session.setAttribute("visitCount", visitCount);
// 4. 使用 Principal
String username = (principal != null) ? principal.getName() : "anonymous";
// 5. 使用 Locale
String language = locale.getLanguage();
// 构建返回信息
return String.format(
"Remote Address: %s<br>" +
"User-Agent Snippet: %s...<br>" +
"Query Param 'info': %s<br>" +
"Visit Count (Session): %d<br>" +
"Username: %s<br>" +
"Locale Language: %s",
remoteAddr,
(userAgent != null && userAgent.length() > 10) ? userAgent.substring(0, 10) : userAgent,
queryParam,
visitCount,
username,
language
);
}
// 示例:直接写入响应流 (方法返回类型为 void)
@GetMapping("/direct-write")
public void directWriteResponse(HttpServletResponse response) throws IOException {
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
writer.println("This response is written directly using HttpServletResponse's writer.");
writer.flush();
// 注意:当直接操作 response 时,不要再返回 String、ModelAndView 或使用 @ResponseBody
}
}
优点:
- 简单直观: 声明即可用。
- 解耦: Controller 方法本身不依赖特定的获取方式。
- 易于测试: 在单元测试中,可以轻松地模拟这些接口(如使用 Mockito 创建 Mock 对象)并传递给方法。
- 符合 Spring MVC 设计: 这是框架推荐的标准方式。
RequestContextHolder (非 Controller 组件中可能使用)
Spring 还提供了一个 RequestContextHolder
类,它使用 ThreadLocal
来持有当前线程的请求属性(包括 HttpServletRequest
和 HttpServletResponse
)。在请求处理流程中的任何地方(只要还是在同一个请求处理线程内)访问这些对象。
但是,在 Controller 中,强烈不推荐使用 RequestContextHolder
,因为方法参数注入是更清晰、更易测试的方式。
RequestContextHolder
主要用于那些不在 Controller 层,但又需要访问当前请求信息的组件(例如某些 Service、Aspect 或 Filter 中,尽管在这些地方通常也应该优先考虑依赖注入或传递参数)。
示例 (仅作演示,不推荐在 Controller 中使用):
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
// ... 在某个方法内部 ...
public void someMethodNotInController() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
HttpSession session = request.getSession(false); // 获取 session,如果不存在则返回 null
// 现在可以使用 request 和 session 对象了
String userAgent = request.getHeader("User-Agent");
if (session != null) {
Object userAttribute = session.getAttribute("user");
// ...
}
System.out.println("Accessed request outside controller via RequestContextHolder. User-Agent: " + userAgent);
} else {
System.out.println("Not in a web request context or context is not Servlet-based.");
}
}
缺点(尤其是在 Controller 中):
- 代码耦合: 严重依赖 Spring 的静态方法,不易单元测试(需要模拟静态方法或设置 RequestContextHolder)。
- 可读性差: 不如方法参数明确。
- 可能出错: 如果在非请求处理线程中调用,会返回
null
或抛出异常。
不推荐的方式:直接 @Autowired
虽然技术上可能通过配置 request/session scope 的代理 Bean 来实现将 HttpServletRequest
等直接 @Autowired
到 Controller 字段,但这通常被认为是不好的实践:
- 线程安全问题: Controller 默认是单例(Singleton)的,而
HttpServletRequest
,HttpServletResponse
,HttpSession
是与每个请求相关的(Request/Session Scoped)。直接注入需要 Spring 创建线程安全的代理对象,增加了复杂性。 - 可测试性差: 字段注入比方法注入更难模拟。
- 不清晰: 不如方法参数那样明确地表明方法对这些对象的依赖。
// 不推荐的示例
@Controller
public class BadPracticeController {
@Autowired // 不推荐!需要特殊配置(如 request scope proxy)才能工作,且有缺点
private HttpServletRequest request;
@GetMapping("/bad-autowire")
@ResponseBody
public String handle() {
// 使用注入的 request 字段
return "Request URI from autowired field: " + request.getRequestURI();
}
}
总结与建议
- 首选且推荐的方式: 在 Controller 方法签名中直接声明
HttpServletRequest
,HttpServletResponse
,HttpSession
等所需参数,利用 Spring MVC 的方法参数注入。 - 优先使用 Spring MVC 抽象: 尽可能使用
@RequestParam
,@PathVariable
,@RequestHeader
,@CookieValue
,@RequestBody
,Model
,RedirectAttributes
,ResponseEntity
等高级抽象,它们能覆盖大部分场景,使代码更简洁、更专注于业务逻辑,并与 Servlet API 解耦。 - 仅在必要时访问 Servlet API: 当 Spring 的抽象无法满足特定需求时(例如需要非常底层的控制、访问非标准 Header/Cookie 属性、直接操作输入/输出流等),才考虑直接注入 Servlet API 对象。
- 避免
RequestContextHolder
: 在 Controller 中尽量避免使用RequestContextHolder
。 - 避免
@Autowired
Servlet API 对象: 不要将请求/会话作用域的对象直接自动装配到单例 Controller 字段中。