跳到主要内容Struts2 请求处理流程源码深度解析 | 极客日志Javajava
Struts2 请求处理流程源码深度解析
Struts2 基于 WebWork 核心构建,是一个成熟的 Web 框架。其请求处理流程始于客户端发起请求,经过 StrutsPrepareAndExecuteFilter 拦截。过滤器初始化 Dispatcher 并加载配置文件,随后创建 ActionContext 上下文。通过 ActionMapper 解析请求路径生成 ActionMapping,确定命名空间与 Action 名称。ActionProxy 负责调用配置管理器获取 Action 实例,并通过拦截器链执行业务逻辑。最终根据结果配置渲染视图。深入理解源码有助于掌握框架底层机制,避免仅停留在使用层面。
Struts2 是 Struts 社区和 WebWork 社区的共同成果,可以视为 WebWork 的升级版。它采用 WebWork 的核心机制,运行稳定、性能优异且设计成熟。
我们以 Struts 2.3.15.1 版本为例进行源码分析。解压源码后,核心逻辑主要集中在 struts-2.3.15.1\src\core\src\main\java\org\apache\struts2 目录下。虽然目录繁多,但理解其包结构有助于快速定位功能。
核心包说明
| 包名 | 说明 |
|---|
| org.apache.struts2.components | 封装视图组件,支持主题(theme)自定义,如 updownselect、datetimepicker 等 |
| org.apache.struts2.config | 定义配置相关的接口和类,XML/Properties 解析主要由 WebWork 完成 |
| org.apache.struts2.dispatcher | 核心包,包含最重要的类,负责请求分发 |
| org.apache.struts2.impl | 定义了 StrutsActionProxy 等对 xwork 扩展的类 |
| org.apache.struts2.interceptor | 定义内置拦截器 |
| org.apache.struts2.servlet | 实现 principalproxy 接口 |
| org.apache.struts2.util | 实用工具包 |
| org.apache.struts2.views | 提供 Freemarker、JSP、Velocity 等页面呈现支持 |
根目录下还有几个关键类:
- StrutsStatics:存放框架常数。
- RequestUtils:请求处理程序,用于检索 Servlet 路径。
- ServletActionContext:获取网站特定的上下文信息。
- StrutsConstants:存储和检索配置设置的中心位置。
- StrutsException:通用运行时异常类。
请求处理架构
一个典型的 Struts2 请求处理流程大致如下:
- 客户端向 Servlet 容器(如 Tomcat)发起请求。
- 请求经过一系列过滤器(Filter),其中
ActionContextCleanUp 是可选的,有助于与其他框架集成。
StrutsPrepareAndExecuteFilter 被调用,询问 ActionMapper 判断是否需要调用 Action。
- 若需要,请求交由
ActionProxy 处理。
ActionProxy 通过 Configuration Manager 查找配置文件中的 Action 类。
- 创建
ActionInvocation 实例。
- 在调用 Action 前后,执行相关拦截器链。
- Action 执行完毕后,根据
struts.xml 配置找到返回结果(如 JSP 或 FreeMarker 模板)并渲染。
初始化阶段
在 web.xml 中注册并映射 Struts2 过滤器是第一步。推荐使用 StrutsPrepareAndExecuteFilter(从 2.1.3 版本起替代了旧的 FilterDispatcher)。
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
当 Web 容器启动时,会初始化该过滤器并执行 init 方法。这里主要完成了 Dispatcher 的创建与初始化。
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();
Dispatcher dispatcher = null;
try {
FilterHostConfig config = new FilterHostConfig(filterConfig);
init.initLogging(config);
dispatcher = init.initDispatcher(config);
init.initStaticContentLoader(config, dispatcher);
prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
postInit(dispatcher, filterConfig);
} finally {
if (dispatcher != null) {
dispatcher.cleanUpAfterInit();
}
init.cleanup();
}
}
其中 initDispatcher 方法负责读取 filterConfig 中的参数,构建 Map 并实例化 Dispatcher 对象。随后调用 dispatcher.init() 加载核心配置文件,包括 default.properties、struts-default.xml、struts-plugin.xml 和用户配置的 struts.xml 等。
请求处理阶段
当用户访问 Action 时,核心过滤器 StrutsPrepareAndExecuteFilter 的 doFilter 方法被触发。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
prepare.setEncodingAndLocale(request, response);
prepare.createActionContext(request, response);
prepare.assignDispatcherToThread();
if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
request = prepare.wrapRequest(request);
ActionMapping mapping = prepare.findActionMapping(request, response, true);
if (mapping == null) {
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
chain.doFilter(request, response);
}
} else {
execute.executeAction(request, response, mapping);
}
}
} finally {
prepare.cleanupRequest(request);
}
}
1. 上下文准备
prepare.setEncodingAndLocale 仅做辅助工作,设置请求编码和 Locale。prepare.createActionContext 则至关重要,它创建了一个线程本地变量 ActionContext。这是一个容器,存储 request、session、application、parameters 等信息。由于使用了 ThreadLocal,不同 Action 之间不会共享上下文,天然避免了线程安全问题。
2. 请求包装
prepare.wrapRequest 会根据请求内容类型包装 Request 对象。如果是文件上传(multipart/form-data),会包装成 MultiPartRequestWrapper;否则包装为 StrutsRequestWrapper。这确保了后续处理能正确获取表单数据。
3. 映射解析
prepare.findActionMapping 通过 ActionMapper 解析请求路径,生成 ActionMapping 对象。这里涉及命名空间(Namespace)和 Action 名称的提取。
以 DefaultActionMapper 为例,它会先去除 URL 后缀,然后分离命名空间和 Action 名。命名空间匹配规则比较灵活:
- 默认情况下,如果 URL 中没有指定命名空间,使用
/。
- 可以通过常量
struts.mapper.alwaysSelectFullNamespace 控制是否强制精确匹配。
- 支持模糊匹配,例如配置了
/common,URL 中的 /common/home 也能匹配到 /common。
- 如果允许动态方法调用(如
userAction!getAll.action),还会进一步分离出方法名。
4. 执行 Action
一旦获取到有效的 ActionMapping,就会调用 execute.executeAction,最终委托给 Dispatcher.serviceAction。
public void serviceAction(HttpServletRequest request, HttpServletResponse response,
ServletContext context, ActionMapping mapping) throws ServletException {
Map<String, Object> extraContext = createContextMap(request, response, mapping, context);
ValueStack stack = ...;
extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class)
.createActionProxy(namespace, name, method, extraContext, true, false);
if (mapping.getResult() != null) {
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
proxy.execute();
}
}
在这里,ActionProxyFactory 创建了 ActionProxy,进而通过 ActionInvocation 执行拦截器链和业务逻辑。最后通过 Result 完成页面跳转。
深入理解这些源码细节,能帮助我们在实际开发中更好地排查问题,而不是仅仅停留在配置层面。开源框架的价值在于知其然更知其所以然。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online