版权声明
- 本文原创作者:谷哥的小弟
- 作者博客地址:http://blog.csdn.net/lfdfhl

一、引言
在 Spring Web MVC(特别是早期基于 JSP 的视图技术)中,国际化(i18n)、主题(Theme)解析、上下文路径获取 等 Web 层通用功能需要一种机制,能够在视图层(如 JSP 页面)中便捷地访问当前 HTTP 请求的上下文信息。为此,Spring 提供了 org.springframework.web.servlet.support.RequestContext 类。
RequestContext 并非直接由开发者频繁实例化,而是作为 JSP 标签库(如 Spring Taglib)和工具方法的底层支撑组件 ,将 HttpServletRequest、ServletContext、MessageSource、ThemeSource 等关键对象封装在一个统一的上下文中,使得视图层可以以类型安全、解耦的方式访问 Web 应用的运行时状态。
本文将对 RequestContext 进行全面、深入、严谨的技术剖析。我们将从其设计目标、核心职责、内部结构、关键方法实现(如消息解析、主题获取、上下文路径处理)、与 Spring MVC 组件的集成机制、典型使用场景(尤其是 JSP 中的 spring:message 标签)、线程安全性,到源码关键逻辑逐一展开,并辅以核心代码解读,力求揭示其在 Spring Web 架构中的定位与工程价值。
二、设计目标与核心职责
2.1 设计目标
- 封装 Web 上下文 :聚合
HttpServletRequest、HttpServletResponse、ServletContext; - 提供国际化支持 :通过绑定的
MessageSource解析本地化消息; - 支持主题切换 :集成
ThemeSource实现动态主题资源加载; - 简化 JSP 开发 :为 Spring JSP 标签库(如
<spring:message>)提供后端逻辑; - 解耦视图与控制器:视图无需直接依赖 Servlet API。
2.2 核心职责
| 职责 | 说明 |
|---|---|
| 消息解析(i18n) | 根据当前 Locale 解析 messages.properties 中的键值 |
| 主题获取 | 返回当前请求的主题(Theme 对象) |
| 上下文路径暴露 | 提供 getContextPath()、getRequestUri() 等便捷方法 |
| Web 对象代理 | 封装 request、response、servletContext 的常用操作 |
三、类结构与初始化机制
3.1 类定义
java
package org.springframework.web.servlet.support;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletContext;
import org.springframework.context.MessageSource;
import org.springframework.ui.context.ThemeSource;
java
public class RequestContext {
// 核心字段
private final HttpServletRequest request;
private HttpServletResponse response;
private final ServletContext servletContext;
private final MessageSource messageSource;
private final ThemeSource themeSource;
private Locale locale;
private TimeZone timeZone;
}
3.2 构造函数
java
public RequestContext(HttpServletRequest request,
HttpServletResponse response,
ServletContext servletContext,
MessageSource messageSource) {
this.request = request;
this.response = response;
this.servletContext = servletContext;
this.messageSource = messageSource;
// 从 request 或 messageSource 获取默认 Locale
this.locale = RequestContextUtils.getLocale(request);
this.timeZone = RequestContextUtils.getTimeZone(request);
// 尝试从 WebApplicationContext 获取 ThemeSource
if (messageSource instanceof ThemeSource) {
this.themeSource = (ThemeSource) messageSource;
} else {
this.themeSource = null;
}
}
关键点:
- 依赖注入式构造:所有上下文对象通过构造函数传入;
MessageSource兼作ThemeSource:Spring 的WebApplicationContext同时实现这两个接口;Locale自动推导 :委托给RequestContextUtils.getLocale()。
四、核心方法源码剖析
4.1 消息解析:getMessage(String code, Object[] args, String defaultMessage, Locale locale)
这是 RequestContext 最核心的功能,支撑 <spring:message> 标签。
java
public String getMessage(String code, @Nullable Object[] args,
@Nullable String defaultMessage, @Nullable Locale locale) {
if (this.messageSource == null) {
return renderDefaultMessage(defaultMessage, args, locale);
}
// 若未指定 locale,则使用当前请求的 locale
if (locale == null) {
locale = this.locale;
}
// 委托给 MessageSource 解析
try {
String msg = this.messageSource.getMessage(code, args, defaultMessage, locale);
return (msg != null ? msg : "");
} catch (NoSuchMessageException ex) {
return renderDefaultMessage(defaultMessage, args, locale);
}
}
辅助方法:renderDefaultMessage
java
private String renderDefaultMessage(@Nullable String defaultMessage,
@Nullable Object[] args, @Nullable Locale locale) {
if (defaultMessage == null) {
return "";
}
// 若有参数,进行格式化(如 {0}, {1})
if (args != null && args.length > 0) {
MessageFormat messageFormat = new MessageFormat(defaultMessage, locale);
return messageFormat.format(args);
}
return defaultMessage;
}
关键机制:
- 委托
MessageSource:实际解析由AbstractMessageSource及其子类(如ResourceBundleMessageSource)完成;- 参数格式化 :使用
java.text.MessageFormat支持占位符替换;- 异常安全 :捕获
NoSuchMessageException,回退到默认消息。
4.2 主题获取:getTheme()
java
public Theme getTheme() {
if (this.themeSource == null) {
return null;
}
// 从 request 中解析主题名(通常通过 Cookie 或 Parameter)
String themeName = RequestContextUtils.getThemeName(this.request);
if (themeName != null) {
return this.themeSource.getTheme(themeName);
}
return null;
}
其中 RequestContextUtils.getThemeName() 实现:
java
// RequestContextUtils.java
public static String getThemeName(HttpServletRequest request) {
// 1. 检查 request attribute
Object themeName = request.getAttribute(THEME_REQUEST_ATTRIBUTE_NAME);
if (themeName instanceof String) {
return (String) themeName;
}
// 2. 回退到 DispatcherServlet 默认主题
return null; // 实际由 ThemeResolver 在 preHandle 中设置
}
流程:
DispatcherServlet在doService()中调用initContextHolders();ThemeResolver(如CookieThemeResolver)解析主题名并存入requestattribute;RequestContext从中读取并获取Theme对象。
4.3 上下文路径与 URL 工具方法
java
public String getContextPath() {
return this.request.getContextPath();
}
public String getRequestUri() {
return this.request.getRequestURI();
}
public String getQueryString() {
return this.request.getQueryString();
}
这些方法为 JSP 提供了无需直接调用 request 的便捷访问。
五、与 Spring MVC 的集成机制
5.1 DispatcherServlet 中的上下文绑定
在每次请求处理前,DispatcherServlet 会将关键对象绑定到 ThreadLocal 或 request attribute:
java
// DispatcherServlet.java
protected void doService(HttpServletRequest request, HttpServletResponse response) {
// ...
WebApplicationContext webAppContext = getWebApplicationContext();
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, webAppContext);
Locale locale = this.localeResolver.resolveLocale(request);
request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME, locale);
String themeName = this.themeResolver.resolveThemeName(request);
request.setAttribute(THEME_REQUEST_ATTRIBUTE_NAME, themeName);
// ...
}
✅ 结果 :
RequestContext可通过request.getAttribute()获取Locale、ThemeName、WebApplicationContext。
5.2 JSP 标签库的后端支撑
以 <spring:message> 为例:
jsp
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<spring:message code="label.username" text="Username"/>
其标签处理器 MessageTag 内部逻辑:
java
// MessageTag.java
@Override
protected void renderMessage(..., PageContext pageContext) {
HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
ServletContext servletContext = pageContext.getServletContext();
WebApplicationContext wac = RequestContextUtils.findWebApplicationContext(
request, servletContext);
RequestContext rc = new RequestContext(request, response, servletContext, wac);
String msg = rc.getMessage(code, arguments, text, null);
pageContext.getOut().print(msg);
}
关键桥梁 :
RequestContextUtils.findWebApplicationContext()从requestattribute 中获取WebApplicationContext,进而构造RequestContext。
六、RequestContextUtils:上下文工具类
RequestContext 的许多静态辅助功能由 RequestContextUtils 提供:
| 方法 | 功能 |
|---|---|
findWebApplicationContext(request, servletContext) |
从 request attribute 或 fallback 查找 WebApplicationContext |
getLocale(request) |
获取当前请求的 Locale(优先从 attribute,否则用 Accept-Language) |
getTimeZone(request) |
获取时区(若 LocaleResolver 实现 TimeZoneAwareLocaleResolver) |
getThemeName(request) |
获取主题名 |
设计意义 :
将上下文查找逻辑集中管理,避免重复代码。
七、典型使用场景
7.1 JSP 中的国际化
jsp
<spring:message code="welcome.message" arguments="${user.name}" />
<!-- 输出:Welcome, Alice! (根据浏览器语言自动切换) -->
7.2 主题资源引用
jsp
<c:if test="${not empty requestContext.theme}">
<link rel="stylesheet" href="<c:url value='/themes/${requestContext.theme.name}/style.css' />" />
</c:if>
注:需在 JSP 中显式暴露
RequestContext:
jsp<spring:bind path="*"> <!-- spring:bind 会自动创建 requestContext 变量 --> </spring:bind>或使用
<spring:htmlEscape>等标签间接创建。
7.3 自定义工具方法
java
public class WebUtils {
public static String getMessage(String code, HttpServletRequest request) {
WebApplicationContext wac = RequestContextUtils.findWebApplicationContext(request);
RequestContext rc = new RequestContext(request, null, request.getServletContext(), wac);
return rc.getMessage(code, null, code, null);
}
}
八、线程安全性与生命周期
- 非线程安全 :
RequestContext绑定到单个 HTTP 请求,应在请求作用域内使用; - 生命周期 :与
HttpServletRequest一致,通常在一次请求处理中创建并销毁; - 无状态设计 :不持有可变共享状态,但内部缓存(如
locale)仅对当前请求有效。
✅ 安全使用 :
每次请求应创建新的
RequestContext实例,不可跨请求共享。
九、局限性与现代替代方案
9.1 局限性
| 问题 | 说明 |
|---|---|
| 强依赖 JSP | 在 Thymeleaf、Freemarker 等现代模板引擎中较少直接使用 |
| Servlet API 耦合 | 无法用于非 Web 环境(如 Reactive 编程) |
| 功能被更高层抽象覆盖 | Spring Boot + Thymeleaf 中 #{...} 直接集成 MessageSource |
9.2 现代替代方案
-
Thymeleaf :
th:text="#{welcome.message(${user.name})}"; -
Spring Boot
@ConfigurationProperties:类型安全配置绑定; -
MessageSource直接注入 :java@Controller public class MyController { @Autowired private MessageSource messageSource; public String handler(Locale locale) { return messageSource.getMessage("key", null, locale); } }
结论 :
RequestContext是 Spring MVC 传统 Web 开发(JSP 时代)的重要组件,在现代应用中虽使用减少,但其设计思想(上下文封装、解耦视图)仍具参考价值。
十、总结
RequestContext 是 Spring Web MVC 中一个精巧的上下文封装器,其核心价值在于:
- 统一 Web 上下文访问:聚合请求、响应、ServletContext、MessageSource;
- 支撑视图层国际化:为 JSP 标签提供消息解析能力;
- 集成主题机制:实现动态主题切换;
- 解耦视图与 Servlet API:提升视图代码的可测试性与可维护性。
| 维度 | 关键结论 |
|---|---|
| 核心功能 | 消息解析(i18n)、主题获取、上下文路径暴露 |
| 依赖组件 | MessageSource, ThemeSource, LocaleResolver |
| 集成方式 | 通过 DispatcherServlet 绑定上下文,JSP 标签库调用 |
| 生命周期 | 单请求作用域,非线程安全 |
| 现代地位 | JSP 时代核心组件,现代模板引擎中逐渐被替代 |
最终建议 :
在维护基于 JSP 的 Spring MVC 应用时,理解
RequestContext有助于掌握国际化与主题机制;在新项目中,应优先采用现代模板引擎的内置 i18n 支持或直接注入MessageSource。