Struts2入门实战项目:HelloWorld案例详解

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:Struts2是用于构建企业级Java Web应用的主流框架,遵循MVC设计模式,具有模块化、可扩展性强等特点。本文通过经典的“HelloWorld”案例,系统讲解Struts2的核心组件与工作流程,包括Action类的定义、struts.xml配置、拦截器机制、结果类型映射以及视图渲染。读者将通过完整的代码示例和运行步骤,掌握Struts2的基本开发模式,为深入学习Web层框架打下坚实基础。

Struts2框架核心概念与MVC架构解析

在现代Java Web开发中,我们常常会遇到这样的问题:为什么一个简单的表单提交,背后却要写一堆看似重复的代码? 🤔 为什么页面跳转总是那么“脆弱”,稍微改个名字就404了?其实啊,这些问题的背后,正是因为我们缺少一套 清晰、规范、可维护 的开发模式。而Struts2,就是为了解决这类痛点而诞生的经典MVC框架。

它不像Spring那样包罗万象,也不像Servlet那样原始裸露,而是介于两者之间——既提供了足够的抽象来提升效率,又保留了对底层流程的控制力。可以说,Struts2是早期Java EE时代Web开发的一把“瑞士军刀”。🛠️ 尽管如今它的风头已被Spring Boot盖过,但在许多遗留系统和教学场景中,依然是不可绕开的重要一环。

那今天咱们就不走寻常路,不照本宣科地念定义,而是从一个最朴素的问题出发:

当用户点击“登录”按钮时,Struts2到底干了啥?

带着这个问题,我们一起深入Struts2的世界,看看它是如何通过MVC分层、拦截器机制和OGNL表达式,把一次HTTP请求变成一次优雅的应用逻辑执行的。


Struts2框架概述:不只是过滤器那么简单

提到Struts2,很多人第一反应是 struts.xml 或者Action类,但真正掌控全局的,其实是那个默默无闻的前端控制器—— StrutsPrepareAndExecuteFilter 。这个名字有点长,但它做的事情非常关键: 拦截所有进入应用的HTTP请求,并启动整个MVC流程

你可能会问:“这不就是一个Filter吗?Servlet里也能写啊。”
没错,但它不是普通的Filter,它是整个Struts2宇宙的“大爆炸起点”。💥

这个Filter会在容器启动时加载 struts-default.xml (内置配置),然后读取你的 struts.xml ,构建出一张完整的“路由地图”。每当有请求进来,比如 /user/login.action ,它就会根据这张地图找到对应的Action类,创建实例,调用方法,最后决定跳转到哪个页面。

整个过程就像一位经验丰富的导游,全程带你游览后台世界的每一个角落,而且还不让你迷路。

举个例子:

<action name="login" class="com.example.LoginAction">
    <result name="success">/dashboard.jsp</result>
    <result name="input">/login.jsp</result>
</action>

当你访问 /login.action 时,Struts2会自动实例化 LoginAction ,调用其 execute() 方法。如果返回 "success" ,就跳转到仪表盘;如果是 "input" ,说明校验失败,回到登录页。

这一切都不需要你在代码里手动写 request.getRequestDispatcher().forward() ,完全由框架帮你调度完成。👏

更妙的是,这套机制基于 插件化设计 ,你可以自定义拦截器、结果类型、类型转换器……几乎每个环节都可以扩展。这就让Struts2既有“开箱即用”的便利性,又有“深度定制”的灵活性。


MVC在Struts2中的实现:谁才是真正的主角?

说到MVC,大家都知道是Model-View-Controller三剑客。但在Struts2中,这三个角色的分工有点“反直觉”。

Model:不只是数据载体,更是业务中枢

传统理解中,Model就是POJO(Plain Old Java Object),比如User、Order这种实体类。但在Struts2里, Action类本身承担了Model的角色

也就是说,你的 LoginAction 不仅接收参数、处理逻辑,还保存状态信息。它既是“控制器”,又是“模型”。

public class LoginAction extends ActionSupport {
    private String username;
    private String password;

    public String execute() {
        // 业务逻辑在这里
        if (userService.authenticate(username, password)) {
            return SUCCESS;
        } else {
            addActionError("账号或密码错误");
            return ERROR;
        }
    }

    // getter/setter...
}

看到没?这个类既有属性(username/password),又有行为(execute),完全是典型的面向对象设计。而且这些属性会被自动填充,无需手动 request.getParameter()

这就是Struts2的聪明之处:把Action当作一个“活”的模型对象,而不是冷冰冰的数据传输容器。

View:JSP + 标签库 = 动态视图引擎

视图层通常是JSP页面,配合Struts2提供的标签库使用。这些标签不仅仅是HTML生成工具,更是与值栈(ValueStack)交互的桥梁。

比如这一行:

<s:property value="username"/>

看起来只是输出用户名,实际上它是在OGNL上下文中查找 username 属性。而这个属性可能来自Action,也可能来自ValueStack中的其他对象。

更重要的是,Struts2标签自带主题支持,默认使用 xhtml 主题,能自动生成带label、error提示的完整表单结构,省去了大量手写HTML的麻烦。

当然啦,如果你追求极致自由,也可以切到 simple 主题,去掉所有额外包装,自己用CSS重新设计样式。🎨

Controller:隐形的指挥官

控制器在Struts2中并不是某个具体的类,而是由 框架内核 + 拦截器链 共同构成的一个“分布式控制器”。

它没有显式的 if-else 路由判断,而是通过 struts.xml 中的配置来声明式地定义流程。比如:

<interceptor-ref name="validation"/>
<interceptor-ref name="workflow"/>

这两个拦截器组合起来,就能实现“先校验再执行”的控制流。如果没有错误,继续往下走;如果有错,直接跳转回input页面,连 execute() 都不调用。

这种AOP式的控制方式,比传统的if-else干净太多了。✅


核心组件协同机制:一场精密的交响乐演奏

想象一下,一次HTTP请求进入Struts2应用,就像一位观众走进音乐厅。接下来会发生什么?

  1. 入场检票 StrutsPrepareAndExecuteFilter 拦截请求,确认是否属于本应用管辖范围;
  2. 引导入座 :根据URL匹配namespace和action name,定位到目标Action;
  3. 准备乐器 :拦截器链依次执行,比如参数绑定、文件上传、权限检查;
  4. 正式演奏 :调用Action的 execute() 方法,执行业务逻辑;
  5. 谢幕退场 :根据返回结果,选择对应视图进行渲染,最终将HTML送回浏览器。

整个过程行云流水,各个组件各司其职,彼此解耦却又紧密协作。而这其中最关键的“指挥棒”,就是 值栈(ValueStack)和OGNL表达式语言

我们可以画个简图来直观感受一下这个流程:

graph TD
    A[HTTP Request] --> B{StrutsPrepareAndExecuteFilter}
    B --> C[创建ValueStack]
    C --> D[执行拦截器链]
    D --> E[params拦截器注入参数]
    E --> F[validation拦截器校验数据]
    F --> G{是否有错误?}
    G -->|是| H[返回input视图]
    G -->|否| I[执行Action.execute()]
    I --> J[返回result字符串]
    J --> K[查找Result映射]
    K --> L[渲染JSP视图]
    L --> M[Response输出]

是不是感觉特别清晰?每一站都有明确的任务,每一步都可追踪、可调试。

尤其是那个 值栈(ValueStack) ,简直是Struts2的灵魂所在。它就像一个临时仓库,把Action、Model、Session等各种数据统统放进去,供OGNL随时取用。

而OGNL呢?它就像是一个超级查询语言,不仅能访问属性、调用方法,还能做集合投影、条件判断,甚至调用静态方法!🤯

有了这对黄金搭档,JSP页面再也不用写Java脚本了,真正做到“展示逻辑与业务逻辑分离”。


等等,说到OGNL和Action,这部分内容太重要了,值得单独拎出来细讲。咱们不妨换个角度,从开发者日常编码的实际体验出发,深入聊聊 Action类的设计哲学与OGNL的魔法世界

毕竟,这才是我们每天打交道最多的地方,也是最容易写出“屎山代码”的重灾区。😅

Action类的设计与OGNL表达式应用

有没有觉得有时候写Action就像在填表格?字段一个接一个,getter/setter复制粘贴,validate方法写得头晕眼花……到最后自己都忘了哪个字段对应哪个校验规则。🙈

别急,这说明你还没真正掌握Struts2的设计精髓。让我们从最基础的问题开始:

为什么我们要继承ActionSupport?不能直接new一个Action出来吗?

好问题!这背后其实藏着一条重要的软件工程原则: 组合优于继承?还是继承优于组合?

2.1 ActionSupport基类的作用与继承机制

2.1.1 Action接口与ActionSupport的区别

首先,我们得明白一件事:Struts2的Action本质上是一个 约定接口 ,而不是强制契约。

原始的 Action 接口长得极简:

public interface Action {
    String SUCCESS = "success";
    String NONE = "none";
    String ERROR = "error";
    String INPUT = "input";
    String LOGIN = "login";

    String execute() throws Exception;
}

就这么点东西。你要实现它,就得自己搞定所有事情——错误收集、国际化消息、日志记录、输入校验……全靠手工打造。

但现实项目哪有这么理想?每个Action都要重复写 List<String> errors = new ArrayList<>() ?累不死你也烦死你。

于是, ActionSupport 出现了。它不是一个接口,而是一个 提供了默认实现的抽象基类

public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider {
    protected List<String> actionErrors;
    protected List<String> fieldErrors;
    protected Map<String, Object> actionMessages;

    public void validate() { }

    public String execute() throws Exception {
        return SUCCESS;
    }

    public boolean hasErrors() { ... }
    public void addActionError(String error) { ... }
}

看到没?它一口气实现了五个关键接口:
- Validateable :支持校验
- ValidationAware :能感知错误状态
- TextProvider :提供文本资源访问能力
- LocaleProvider :获取当前语言环境
- 还有最基本的 Action

这意味着只要你继承了 ActionSupport ,立马就拥有了:
- 错误信息添加 ( addActionError )
- 字段级校验 ( addFieldError )
- 国际化文本获取 ( getText )
- 空实现的 validate() 方法

简直是“开箱即用”的典范!📦

特性 Action接口 ActionSupport
实现方式 必须手动实现全部逻辑 提供默认实现
错误处理 需自行维护错误列表 内置 addActionError() 等方法
校验支持 无内置validate() 提供空实现的 validate() 方法
国际化支持 不支持 实现 TextProvider 接口
返回值常量 定义基本常量 继承并扩展使用

所以说, ActionSupport 不是为了“强制你继承”,而是为了 减少样板代码,提升开发效率 。这才是它的真正价值所在。

不过话说回来,有人可能会担心:“这不是违反了‘组合优于继承’的原则吗?”

嗯,问得好。但请注意, ActionSupport 并没有强迫子类必须覆盖构造函数或初始化流程,而是依赖Struts2容器通过反射和依赖注入来装配上下文。因此,它更像是一个 功能聚合体 ,而非严格的继承关系。💡


2.1.2 validate()与execute()方法的执行流程

接下来,我们来揭开一个经常被误解的谜团:

validate() 和 execute() 到底谁先执行?它们是怎么联动的?

很多人以为这是Action自己的行为,其实不然。这一切都是由 拦截器链 控制的!

具体来说,是两个拦截器联手完成的:
- validation 拦截器:负责调用 validate() 方法
- workflow 拦截器:负责决定是否跳过 execute()

下面是它们协作的真实流程:

graph TD
    A[请求进入] --> B{Action是否实现Validateable?}
    B -->|是| C[调用validate()]
    B -->|否| D[跳过校验]
    C --> E{是否有错误?}
    E -->|有| F[设置result=input]
    E -->|无| G[执行execute()]
    F --> H[跳转至input视图]
    G --> I[返回结果]
    I --> J[查找对应result视图]

注意几个关键点:
1. 只有实现了 Validateable 接口的类才会触发 validate() ,而 ActionSupport 正好实现了它;
2. validation 拦截器运行在 params 之后,确保参数已经注入完毕再进行校验;
3. 如果发现错误(比如调用了 addFieldError() ), workflow 拦截器会中断流程,直接返回 input 结果,根本不会进入 execute()

这就避免了“明明表单都没填对,还跑去数据库查一遍”的尴尬局面。👏

来看个真实例子:

public class LoginAction extends ActionSupport {
    private String username;
    private String password;

    public void validate() {
        if (username == null || username.trim().isEmpty()) {
            addFieldError("username", "请输入用户名");
        }
        if (password == null || password.length() < 6) {
            addFieldError("password", "密码至少6位");
        }
    }

    public String execute() {
        if ("admin".equals(username) && "123456".equals(password)) {
            return SUCCESS;
        } else {
            addActionError("登录失败,请检查账号密码");
            return ERROR;
        }
    }
}

这段代码的执行路径可能是:
- 用户提交空表单 → validate() 添加错误 → 跳转回登录页显示红字提示;
- 用户密码太短 → 同样走 input 路径;
- 用户名密码正确 → validate() 无错 → 执行 execute() → 成功跳转;
- 用户名密码错误 → execute() 返回 ERROR → 显示全局错误。

完美闭环!🎯

而且,Struts2还支持 方法级校验 。比如你有个Action有多个方法:

public void validateSave() { ... }
public String save() { return SUCCESS; }

public void validateDelete() { ... }
public String delete() { return SUCCESS; }

只有当调用 save.action 时,才会触发 validateSave() ,避免不必要的校验开销。


2.1.3 数据校验与错误信息封装实践

说到校验,光会用 addFieldError 还不够,你还得知道怎么组织错误信息才够专业。

编程式校验 vs 声明式校验

Struts2提供两种校验方式:
- 编程式校验 :在 validate() 方法里写Java代码,适合复杂业务规则;
- 声明式校验 :用XML或注解定义规则,适合通用约束(如非空、长度、邮箱格式)。

对于新手,建议先掌握编程式校验,因为它更直观,也更容易调试。

比如跨字段校验:

@Override
public void validate() {
    if (username != null && username.equals(password)) {
        addFieldError("password", "密码不能与用户名相同");
    }
    if (!StringUtils.contains(email, "@")) {
        addFieldError("email", "邮箱格式不正确");
    }
}

这些错误信息最终都会被放入ValueStack,供JSP页面使用。

国际化支持:让错误提示说“人话”

硬编码错误消息是非常危险的行为。想想看,万一哪天产品经理说:“我们要做国际化版本!” 你就得满代码找字符串替换。

所以正确的做法是: 把错误消息外置到资源文件中

创建 ApplicationResources.properties

username.required=用户名不能为空
password.tooShort=密码长度不能少于6位

中文版 ApplicationResources_zh_CN.properties

username.required=请输入用户名
password.tooShort=密码至少6位

然后在Action中引用:

addFieldError("username", getText("username.required"));

getText() 会根据当前用户的Locale自动选择合适的语言版本,轻松实现多语言切换。

错误信息展示方式对比
错误类型 添加方法 JSP标签 用途说明
字段错误 addFieldError() <s:fielderror> 显示具体字段的校验错误
动作错误 addActionError() <s:actionerror> 显示整体操作失败原因
消息提示 addActionMessage() <s:actionmessage> 显示成功或其他提示信息

合理搭配使用,可以做出用户体验极佳的反馈界面。👍


2.2 OGNL表达式在Action数据传递中的角色

如果说Action是演员,那OGNL就是舞台上的聚光灯——它决定了观众(JSP页面)能看到什么、怎么看到。

2.2.1 OGNL上下文结构与值栈(ValueStack)原理

OGNL全称是Object-Graph Navigation Language,听着很高大上,其实就是一种 对象图导航语言 。它可以让你用一行表达式访问深层嵌套的对象属性,甚至调用方法、操作集合。

而在Struts2中,OGNL的上下文就是 ValueStack

每个请求都会有一个独立的ValueStack实例,它包含两部分:
- Root栈 :存放Action、Model等对象,OGNL默认从此处查找;
- Context映射 :包含request、session、parameters等作用域变量。

graph TB
    subgraph ValueStack
        root[Root Stack]
        context[Context Map]

        root --> action1[HelloAction]
        root --> model[UserModel]

        context --> session[Session Scope]
        context --> request[Request Scope]
        context --> parameters[Parameters]
        context --> attr[Page/Application Attributes]
    end

当你在JSP中写:

<s:property value="name"/>

Struts2会去ValueStack的Root栈顶找 name 属性。如果找不到,再去Context里找,比如 #session.user.name

这种设计的好处是: 你不需要显式地把数据塞进request域 ,只要放在Action里,就能被自动访问。


2.2.2 在JSP中通过OGNL访问Action属性

OGNL的强大之处在于它的表达式语法。下面是一些常用技巧:

表达式 说明
name 访问Action的 getName() 方法
user.address.city 多层嵌套属性访问
users[0].name 访问List集合第一个元素的name属性
scores['math'] 访问Map类型的scores中键为”math”的值
friends.{name} 投影操作:提取所有friend对象的name组成新列表
items.{? #this.price > 100} 选择操作:筛选价格大于100的项

举个实际例子:

public class ProductAction extends ActionSupport {
    private List<Product> products;

    public String execute() {
        products = Arrays.asList(
            new Product("iPhone", 999),
            new Product("iPad", 799),
            new Product("iMac", 1999)
        );
        return SUCCESS;
    }

    public List<Product> getProducts() { return products; }
}

在JSP中可以这样渲染:

<!-- 显示所有产品名称 -->
<s:iterator value="products.{name}" var="n">
    <p><s:property/></p>
</s:iterator>

<!-- 显示高价产品 -->
<s:iterator value="products.{? #this.price > 800}">
    <p><s:property value="name"/>: ¥<s:property value="price"/></p>
</s:iterator>

看到了吗?不用写Java代码,仅靠OGNL就能完成集合的筛选和投影。这在JSP中简直是降维打击!😎


2.2.3 类型转换与集合操作的OGNL实现

Struts2内置了强大的类型转换机制。比如前端传过来的字符串 "true" ,能自动转成boolean; "2024-01-01" 能转成Date。

甚至连复选框都能自动组装成List:

<input type="checkbox" name="hobbies" value="reading"/>
<input type="checkbox" name="hobbies" value="music"/>

只要Action中有:

private List<String> hobbies;
public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; }

Struts2就会把多个同名参数合并为一个List,自动赋值。✨

此外,OGNL还支持动态构造对象:

<s:set var="now" value="@java.util.Calendar@getInstance().time"/>
当前时间:<s:date name="#now" format="yyyy-MM-dd HH:mm:ss"/>

这里调用了静态方法获取当前时间,并格式化输出。

常见OGNL操作对照表
操作类型 示例表达式 说明
静态方法调用 @Class@method() @Math@random()
静态字段访问 @Class@FIELD @java.lang.Boolean@TRUE
构造新对象 new java.util.ArrayList() 可在表达式内创建实例
条件运算 score > 80 ? '优秀' : '一般' 支持三元操作符
方法调用 userName.toUpperCase() 调用对象方法

这些特性极大增强了视图层的表现力,减少了Java代码的侵入性。


2.3 实践:构建HelloWorld对应的Action类

理论说了这么多,不如动手写个HelloWorld来得实在。🚀

2.3.1 编写继承ActionSupport的HelloAction

package com.example.action;

import com.opensymphony.xwork2.ActionSupport;

public class HelloAction extends ActionSupport {
    private String message;
    private String name;

    @Override
    public String execute() throws Exception {
        if (name == null || name.trim().isEmpty()) {
            name = "World";
        }
        message = "Hello, " + name + "!";
        return SUCCESS;
    }

    // Getter and Setter
    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

重点解释:
- 第7–8行:定义属性,用于接收参数和存储结果;
- 第11–17行: execute() 中判断 name 是否为空,设置默认值并构造问候语;
- 第19–30行:标准JavaBean的getter/setter方法,供OGNL访问。

注意:Struts2通过反射调用setter方法注入参数,所以必须保证setXxx方法存在。


2.3.2 定义业务逻辑与返回结果常量

虽然可以直接写 return "success"; ,但最好统一使用常量:

public class Constants {
    public static final String SUCCESS = "success";
    public static final String INPUT = "input";
    public static final String ERROR = "error";
}

不过更常见的做法是直接继承 ActionSupport 里的常量:

return INPUT; // 通常用于返回表单页

也可以自定义结果类型:

<result name="welcome">/WEB-INF/pages/welcome.jsp</result>

并在Action中返回:

return "welcome";

2.3.3 调试Action执行过程的日志输出

为了观察执行流程,可以在 struts.xml 中启用开发模式:

<constant name="struts.devMode" value="true"/>

同时添加日志输出:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class HelloAction extends ActionSupport {
    private static final Log LOG = LogFactory.getLog(HelloAction.class);

    @Override
    public String execute() {
        LOG.info("开始执行HelloAction.execute()");
        LOG.debug("接收到name参数:" + name);

        // 业务逻辑...

        LOG.info("生成消息:" + message);
        return SUCCESS;
    }
}

确保日志配置正确后,访问 /hello.action?name=Tom ,你会在控制台看到:

INFO  HelloAction - 开始执行HelloAction.execute()
DEBUG HelloAction - 接收到name参数:Tom
INFO  HelloAction - 生成消息:Hello, Tom!

这对排查参数未注入、方法未执行等问题非常有帮助。🔍


综上所述,Action类设计与OGNL表达式的深度融合构成了Struts2的核心竞争力。掌握这两者的工作机制,不仅能提升开发效率,更能深入理解框架背后的运行逻辑,为后续拦截器、标签库等高级特性的学习打下坚实基础。💪

struts.xml配置与请求映射机制

如果说Action是演员,OGNL是灯光,那 struts.xml 就是整场演出的 剧本和导演手册 。🎭

它决定了谁在什么时候出场,说什么台词,最后走向哪个结局。没有它,整个MVC流程就乱套了。

可问题是,这份“剧本”该怎么写?怎么才能让它既清晰又灵活?别急,咱们一步步来拆解。

3.1 struts.xml文件的基本结构与DTD约束

struts.xml 是Struts2的主配置文件,通常放在 src/main/resources 目录下。它是整个应用的“中枢神经”,负责定义:
- 请求路径映射
- Action类绑定
- 结果跳转逻辑
- 拦截器栈引用
- 命名空间管理

一个典型的配置长这样:

<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
    "http://struts.apache.org/dtds/struts-2.5.dtd">

<struts>
    <package name="default" extends="struts-default" namespace="/">
        <action name="hello" class="com.example.HelloAction">
            <result name="success">/WEB-INF/jsp/welcome.jsp</result>
        </action>
    </package>
</struts>

我们逐行来看:

元素 参数 说明
<!DOCTYPE ... > - 声明 DTD 约束版本,确保 XML 结构符合 Struts2 规范
<struts> - 根元素,所有配置都必须嵌套在其内部
<package> name 包名,唯一标识一组 Action
extends 继承另一个包(通常是 struts-default )以复用拦截器栈等配置
namespace 定义该包下所有 Action 的 URL 前缀路径
<action> name 请求 URL 中的 action 名称(如 /hello.action
class 对应的 Action 类全限定名
method 可选,指定调用的具体方法,默认为 execute()
<result> name 返回结果逻辑名,需与 Action 中返回值匹配
内容体 实际跳转路径,支持 JSP、FreeMarker、Velocity 等

⚠️ 注意:若未显式声明 class ,则默认使用 ActionSupport ;若未指定 method ,则调用 execute() 方法。

这个配置的意思是:当访问 /hello.action 时,实例化 HelloAction 并执行 execute() ,如果返回 "success" ,就跳转到 /WEB-INF/jsp/welcome.jsp

整个流程如下图所示:

graph TD
    A[HTTP Request: /hello.action] --> B{StrutsPrepareAndExecuteFilter}
    B --> C[查找匹配的 Package]
    C --> D[根据 namespace 和 action name 定位 Action]
    D --> E[实例化 HelloAction]
    E --> F[执行 execute() 方法]
    F --> G{返回结果字符串}
    G -- "success" --> H[查找对应 <result>]
    H --> I[转发至 welcome.jsp]
    G -- "input" --> J[跳转输入页或报错]

是不是特别清晰?每一步都有迹可循。


3.1.2 namespace命名空间的作用与配置策略

随着项目变大,你会发现一个问题:不同模块都想用 list.action edit.action 这种通用名字,怎么办?难道要起名叫 userList.action productList.action ?太丑了!

这时候, namespace 就派上用场了。它就像Java的包机制,给Action划分“行政区”。

例如:

<package name="admin" namespace="/admin" extends="struts-default">
    <action name="list" class="com.example.AdminAction">
        <result>/WEB-INF/jsp/admin/list.jsp</result>
    </action>
</package>

<package name="user" namespace="/user" extends="struts-default">
    <action name="list" class="com.example.UserAction">
        <result>/WEB-INF/jsp/user/list.jsp</result>
    </action>
</package>

现在:
- /admin/list.action → Admin模块
- /user/list.action → User模块

完美解决命名冲突!

而且查找还有优先级规则:
1. 精确匹配优先
2. 逐级回溯(如 /admin/sub 找不到会尝试 /admin
3. 最后 fallback 到根命名空间

✅ 最佳实践建议:
- 使用 /module 形式(如 /product , /order )提高语义清晰度;
- 避免嵌套过深(不超过两级);
- 将公共工具类 Action 放入根命名空间。


3.1.3 默认action与默认result处理机制

为了让系统更健壮,Struts2提供了两种“兜底”机制:

(1)默认 Action(default-action-ref)

当某个命名空间下没有匹配的 Action 时,可以指定一个默认处理程序:

<package name="public" namespace="/site" extends="struts-default">
    <default-action-ref name="notFound"/>
    <action name="home"><result>/home.jsp</result></action>
    <action name="about"><result>/about.jsp</result></action>
    <action name="notFound"><result>/error/404.jsp</result></action>
</package>

访问 /site/nonexistent.action 就会自动跳转到 404 页面。

(2)通配符 Action(Wildcard Mapping)

想不想批量映射一堆Action?比如 /api/user/save /api/order/create

可以用通配符:

<package name="api" namespace="/api" extends="struts-default">
    <action name="*/*" class="com.example.{1}Action" method="{2}">
        <result>/WEB-INF/json.jsp</result>
    </action>
</package>

访问 /api/user/save 就会调用 UserAction.save() 方法,简直不要太爽!😄


3.2 Action请求映射与路径解析规则

3.2.1 URL到Action方法的匹配逻辑

当用户发起请求如 http://localhost:8080/app/hello.action 时,Struts2会:
1. 提取 contextPath、namespace 和 actionName
2. 遍历所有 package 查找匹配项
3. 实例化 Action 并执行目标方法

.action 是默认扩展名,可通过 struts.action.extension 修改,甚至设为空实现伪静态。


3.2.2 method属性指定自定义方法调用

除了 execute() ,还可以直接指定其他方法:

<action name="saveUser" class="com.example.UserAction" method="save"/>
<action name="deleteUser" class="com.example.UserAction" method="delete"/>

这样就能避免在一个Action里塞太多方法,保持职责单一。


3.2.3 动态方法调用(DMI)的安全性考量

Struts2曾支持通过URL直接调用方法:

/hello!sayHi.action

但这带来了严重的安全风险(S2-057漏洞),因此强烈建议关闭DMI:

<constant name="struts.enable.DynamicMethodInvocation" value="false"/>

改用通配符或 method= 属性替代。


3.3 实践:配置HelloWorld应用的请求路由

3.3.1 创建package并声明action映射

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
    "http://struts.apache.org/dtds/struts-2.5.dtd">

<struts>
    <constant name="struts.devMode" value="true"/>

    <package name="helloworld" namespace="/hw" extends="struts-default">
        <action name="hello" class="com.demo.HelloAction">
            <result name="success">/WEB-INF/pages/welcome.jsp</result>
        </action>
    </package>
</struts>

3.3.2 设置success结果跳转至JSP页面

创建 welcome.jsp

<%@ taglib prefix="s" uri="/struts-tags" %>
<h2><s:property value="message"/></h2>

3.3.3 验证struts.xml语法正确性的调试技巧

  • 启用 devMode 查看日志
  • 使用 struts2-config-browser-plugin 可视化查看配置
  • 检查 web.xml 是否注册了 StrutsPrepareAndExecuteFilter

搞定!🎉

拦截器机制与结果类型控制

终于到了Struts2最精彩的部分—— 拦截器(Interceptor)

它就像电影里的“幕后英雄”,默默完成日志记录、权限校验、事务管理等工作,却不抢主角(Action)的风头。

4.1 拦截器工作原理剖析

4.1.1 拦截器栈与执行生命周期钩子

拦截器采用“环绕通知”模式,在Action执行前后插入逻辑:

public String intercept(ActionInvocation invocation) throws Exception {
    System.out.println("Before Action");

    String result = invocation.invoke();

    System.out.println("After Action");

    return result;
}

多个拦截器组成“拦截器栈”,按顺序执行,后置处理逆序执行(LIFO)。

内置的 defaultStack 包含参数绑定、校验、国际化等功能,开箱即用。


4.1.2 常用拦截器详解

拦截器 功能
params 自动注入请求参数
modelDriven 将Model置于ValueStack顶端
validation 执行字段校验

顺序很重要! modelDriven 必须在 params 之前,否则无法正确绑定。


4.1.3 自定义拦截器开发

比如实现权限检查:

public class RoleCheckInterceptor extends AbstractInterceptor {
    private String requiredRole;

    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
        String userRole = (String) ActionContext.getContext().getSession().get("role");
        if (!requiredRole.equals(userRole)) {
            return "forbidden";
        }
        return invocation.invoke();
    }

    public void setRequiredRole(String requiredRole) {
        this.requiredRole = requiredRole;
    }
}

注册后即可在Action中使用:

<interceptor-ref name="roleCheck">
    <param name="requiredRole">ADMIN</param>
</interceptor-ref>

参数通过setter自动注入,超方便!🔧


4.2 结果类型多样化配置

4.2.1 dispatcher、redirect、chain对比

类型 行为 地址栏变化 共享Request
dispatcher 服务器转发

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:Struts2是用于构建企业级Java Web应用的主流框架,遵循MVC设计模式,具有模块化、可扩展性强等特点。本文通过经典的“HelloWorld”案例,系统讲解Struts2的核心组件与工作流程,包括Action类的定义、struts.xml配置、拦截器机制、结果类型映射以及视图渲染。读者将通过完整的代码示例和运行步骤,掌握Struts2的基本开发模式,为深入学习Web层框架打下坚实基础。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

内容概要:本文详细介绍了“秒杀商城”微服务架构的设计与实战全过程,涵盖系统从需求分析、服务拆分、技术选型到核心功能开发、分布式事务处理、容器化部署及监控链路追踪的完整流程。重点解决了高并发场景下的超卖问题,采用Redis预减库存、消息队列削峰、数据库乐观锁等手段保障数据一致性,并通过Nacos实现服务注册发现与配置管理,利用Seata处理跨服务分布式事务,结合RabbitMQ实现异步下单,提升系统吞吐能力。同时,项目支持Docker Compose快速部署和Kubernetes生产级编排,集成Sleuth+Zipkin链路追踪与Prometheus+Grafana监控体系,构建可观测性强的微服务系统。; 适合人群:具备Java基础和Spring Boot开发经验,熟悉微服务基本概念的中高级研发人员,尤其是希望深入理解高并发系统设计、分布式事务、服务治理等核心技术的开发者;适合工作2-5年、有志于转型微服务或提升架构能力的工程师; 使用场景及目标:①学习如何基于Spring Cloud Alibaba构建完整的微服务项目;②掌握秒杀场景下高并发、超卖控制、异步化、削峰填谷等关键技术方案;③实践分布式事务(Seata)、服务熔断降级、链路追踪、统一配置中心等企业级中间件的应用;④完成从本地开发到容器化部署的全流程落地; 阅读建议:建议按照文档提供的七个阶段循序渐进地动手实践,重点关注秒杀流程设计、服务间通信机制、分布式事务实现和系统性能优化部分,结合代码调试与监控工具深入理解各组件协作原理,真正掌握高并发微服务系统的构建能力。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值