跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Struts2 请求处理流程源码深度解析

Struts2 基于 WebWork 核心构建,是一个成熟的 Web 框架。其请求处理流程始于客户端发起请求,经过 StrutsPrepareAndExecuteFilter 拦截。过滤器初始化 Dispatcher 并加载配置文件,随后创建 ActionContext 上下文。通过 ActionMapper 解析请求路径生成 ActionMapping,确定命名空间与 Action 名称。ActionProxy 负责调用配置管理器获取 Action 实例,并通过拦截器链执行业务逻辑。最终根据结果配置渲染视图。深入理解源码有助于掌握框架底层机制,避免仅停留在使用层面。

指针猎手发布于 2025/1/19更新于 2026/6/621 浏览
Struts2 请求处理流程源码深度解析

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 请求处理流程大致如下:

  1. 客户端向 Servlet 容器(如 Tomcat)发起请求。
  2. 请求经过一系列过滤器(Filter),其中 ActionContextCleanUp 是可选的,有助于与其他框架集成。
  3. StrutsPrepareAndExecuteFilter 被调用,询问 ActionMapper 判断是否需要调用 Action。
  4. 若需要,请求交由 ActionProxy 处理。
  5. ActionProxy 通过 Configuration Manager 查找配置文件中的 Action 类。
  6. 创建 ActionInvocation 实例。
  7. 在调用 Action 前后,执行相关拦截器链。
  8. 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);
        // 创建 action 上下文
        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
    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);

    // 执行 Action
    if (mapping.getResult() != null) {
        Result result = mapping.getResult();
        result.execute(proxy.getInvocation());
    } else {
        proxy.execute();
    }
}

在这里,ActionProxyFactory 创建了 ActionProxy,进而通过 ActionInvocation 执行拦截器链和业务逻辑。最后通过 Result 完成页面跳转。

深入理解这些源码细节,能帮助我们在实际开发中更好地排查问题,而不是仅仅停留在配置层面。开源框架的价值在于知其然更知其所以然。

目录

  1. 核心包说明
  2. 请求处理架构
  3. 初始化阶段
  4. 请求处理阶段
  5. 1. 上下文准备
  6. 2. 请求包装
  7. 3. 映射解析
  8. 4. 执行 Action
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • JavaScript Blob 对象详解与常见应用场景
  • CISP-PTE 认证含金量及报考指南
  • 本地快速安装运行开源 LLaMa3 大模型
  • 没有项目经验如何快速转行 AI 产品经理
  • Spring Web MVC 入门:注解用法、参数传递与 Postman 测试
  • 七种主流大模型微调方法详解:原理与场景对比
  • VibeVoice Pro 结合 Whisper+Llama3 构建语音交互链教程
  • VS Code 集成 GitHub Copilot 实战指南
  • Python 基于 Selenium 实现 12306 自动购票脚本
  • C# 后端导出 Excel 并实现前端直接下载方案
  • Edict 三省六部制 OpenClaw 集成封装版使用指南
  • Java 黑马商城微服务实战:分布式架构开发与部署
  • 五年程序员兼职接单经验总结与避坑指南
  • GenAI 技术栈进展与应用案例报告
  • PyTorch 生成式人工智能:StyleGAN 详解与实现
  • AI 绘画商业生态解析:提示词、壁纸变现与 NFT 收藏
  • Python+Django 校园集市管理系统设计与实现
  • Windows 本地运行 DeepSeek 的三步配置指南
  • 中国人工智能大模型技术白皮书核心内容梳理
  • 为什么 AI 难以取代软件工程师?

相关免费在线工具

  • 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