什么是Struts?
Struts是一套基于MVC模式的Java Web应用开源框架,由Apache软件基金会维护。它通过规范请求处理流程、内置标签库和插件机制,显著提升了企业级Web应用的可维护性与扩展性。
- Struts 1:2001年发布,采用单例Action,线程安全依赖开发者注意
- Struts 2:2005年发布,基于WebWork设计,Action为多例,更安全易用
- Struts 2.5.x:2014–2023主流版本;Struts 2.5.32为最后稳定版(2023.11)
为什么学Struts?
尽管Spring MVC、Spring Boot更流行,但国内大量遗留系统仍基于Struts(尤其银行、电信领域)。理解其设计思想对掌握Java Web架构演进至关重要。
- 掌握MVC分层思想,理解请求生命周期
- 深入理解拦截器链、OGNL表达式等高级特性
- 应对国企/传统企业Java岗技术面试高频考点
典型使用场景
适用于需要强规范、长生命周期维护的中大型项目,尤其在以下场景常见:
- 政府OA系统、社保/公积金平台(Java 6/7时代建设)
- 银行核心交易外围接口系统
- 电信计费系统、营业厅前端展示模块
Struts核心概念与组件详解
Q1:Struts请求处理流程是怎样的?请画出关键组件交互图。
Struts请求流程(以Struts2为例):
- 浏览器发起HTTP请求 → FilterDispatcher(或
StrutsPrepareAndExecuteFilter) - Filter拦截请求 → 查找
struts.xml匹配<action>配置 - 创建Action实例(多例)→ 依次执行拦截器栈(Stack)中的
preResult前逻辑 - 调用
ActionSupport的execute()方法 → 返回String结果码 - 根据结果码匹配
<result>类型(如dispatcher跳转JSP) - 执行
postResult拦截器 → 渲染视图 → 返回响应
<action name="user-*" class="com.kaoacca.action.UserAction" method="{1}">
<result name="success">/WEB-INF/jsp/user.jsp</result>
<interceptor-ref name="defaultStack"/>
</action>
关键组件说明:
- Dispatcher:核心控制器,协调各组件
- ActionProxy:代理,创建ActionInvocation
- ActionInvocation:封装Action执行环境,驱动拦截器链
- Result:结果处理器,决定视图渲染方式
Q2:Struts2中Action类有哪几种创建方式?推荐哪种?为什么?
三种创建方式:
| 方式 | 配置说明 | 线程安全 | 推荐度 |
|---|---|---|---|
| 无参构造类 | 默认方式,类中定义execute()方法 |
✅ 多例,线程安全 | ★★★★★ |
实现Action接口 |
需实现execute()等方法 |
✅ 多例,线程安全 | ★★★★☆ |
继承ActionSupport |
提供校验、国际化等增强功能 | ✅ 多例,线程安全 | ★★★★★ |
⚠️ 注意:Struts1采用单例Action,若在Action中定义成员变量会导致线程安全问题,这是其被废弃的关键原因之一。
Q3:struts.xml中常见配置项解析(附实战配置片段)
核心配置项说明:
<struts>
<constant name="struts.devMode" value="true"/>
<constant name="struts.i18n.encoding" value="UTF-8"/>
<package name="default" namespace="/" extends="struts-default">
<interceptors>
<interceptor name="log" class="com.kaoacca.interceptor.LogInterceptor"/>
<interceptor-stack name="myStack">
<interceptor-ref name="log"/>
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="myStack"/>
<default-action-ref name="notFound"/>
<action name="*" class="com.kaoacca.action.*Action" method="{1}">
<result name="success">/WEB-INF/jsp/{1}.jsp</result>
<result name="input">/WEB-INF/jsp/{1}-input.jsp</result>
</action>
<action name="notFound">
<result>/WEB-INF/jsp/404.jsp</result>
</action>
</package>
</struts>
重点参数说明:
namespace="/":定义命名空间,影响URL路径(如/user-list)method="{1}":通配符调用,如user-*匹配user-list调用list()struts.devMode:开发模式,启用热加载与详细错误页struts.i18n.encoding:统一字符编码,防止中文乱码
Struts1与Struts2深度对比
架构设计维度
| 维度 | Struts1 | Struts2 |
|---|---|---|
| Action实例 | 单例(Singleton) | 多例(每请求新建) |
| 线程安全 | ❌ 需避免成员变量 | ✅ 安全 |
| 依赖注入 | 需手动集成Spring | ✅ 原生支持(通过Plugin) |
| 请求封装 | 直接操作HttpServletRequest |
✅ 封装为ValueStack,解耦Servlet API |
| 配置方式 | 仅XML | XML + 注解(@Action) |
💡 设计哲学差异:Struts1强绑定Servlet API,Struts2采用“无侵入式”设计,便于单元测试。
开发体验维度
| 功能 | Struts1 | Struts2 |
|---|---|---|
| 表单验证 | 需重写validate(),代码冗余 |
✅ 支持XML/注解验证,自动回填 |
| 结果类型 | 仅支持JSP | ✅ JSON、Freemarker、Velocity等 |
| OGNL支持 | ❌ 无 | ✅ 核心特性,支持复杂表达式 |
| 标签库 | JSP标签(<html:form>) |
✅ UI标签(<s:form>)+ 自定义 |
实战建议:新项目优先选择Struts2;若维护旧系统,需重点检查validate()方法是否线程安全。
安全与维护性维度
| 风险点 | Struts1 | Struts2 |
|---|---|---|
| OGNL表达式注入 | ❌ 不涉及 | ⚠️ 高危(S2-045等漏洞) |
| 默认拦截器 | 较少 | ✅ 默认含params、conversionError等 |
| 版本生命周期 | 2008年停止维护 | 2023年11月停更(最后版2.5.32) |
| 迁移成本 | 高(需重写大量代码) | 低(继承结构兼容) |
🔒 重要提醒:Struts2因OGNL表达式灵活性导致高危漏洞频发(如S2-045、S2-059),企业生产环境必须定期升级并配置
struts.excludedPatterns。
拦截器(Interceptor)深度解析
Q4:拦截器与Filter有何区别?请从作用范围、执行顺序说明。
核心区别:
| 维度 | Filter(Servlet规范) | Interceptor(Struts2) |
|---|---|---|
| 作用范围 | 所有请求(包括静态资源) | 仅Struts2 Action请求 |
| 配置位置 | web.xml |
struts.xml |
| 执行时机 | 请求进入容器后 | Action执行前后(含preResult) |
| 访问能力 | 可操作HttpServletRequest |
可操作ValueStack、ActionContext |
执行顺序示例:
<interceptor-stack name="myStack"> <interceptor-ref name="log"/> <interceptor-ref name="params"/> <interceptor-ref name="validation"/> <interceptor-ref name="workflow"/> <interceptor-ref name="logResult"/> </interceptor-stack>
Q5:自定义拦截器实战(含参数传递)
步骤1:编写拦截器类
public class AuthInterceptor implements Interceptor {
@Override
public String intercept(ActionInvocation invocation) throws Exception {
Map<String, Object> session = invocation.getInvocationContext().getSession();
if (session.get("user") == null) {
return "login"; // 未登录跳转
}
// 放行前执行逻辑
String result = invocation.invoke(); // 执行后续拦截器/Action
// 放行后执行逻辑
System.out.println("Action执行完成");
return result;
}
}
步骤2:配置拦截器
<interceptors>
<interceptor name="auth" class="com.kaoacca.interceptor.AuthInterceptor"/>
<interceptor-stack name="secureStack">
<interceptor-ref name="auth">
<param name="excludeMethods">login,register</param>
</interceptor-ref>
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
</interceptors>
关键技巧:
excludeMethods:排除不需要拦截的方法(如登录页)includeMethods:仅拦截指定方法- 通过
invocation.getInvocationContext().getSession()获取会话
OGNL表达式与值栈(ValueStack)
Q6:OGNL是什么?在Struts2中如何简化视图开发?
OGNL(Object-Graph Navigation Language)是Struts2的表达式语言,支持:
- 对象属性访问(
user.name) - 方法调用(
user.getAge() → user.age) - 集合操作(
list.{?#this>10}过滤) - 静态方法调用(
@java.lang.Math@max(5,3))
实战示例:
<s:textfield name="user.username" label="用户名"/>
<!-- 自动绑定到Action的user对象 -->
<s:iterator value="userList" var="u">
<s:property value="#u.username"/>
<s:if test="#u.age > 18">成年</s:if>
</s:iterator>
💡 重要提示:OGNL是Struts2安全漏洞的高发区(如S2-045),生产环境必须限制OGNL表达式范围!
Q7:ValueStack结构与数据查找顺序
ValueStack = 栈顶对象 + 模型对象 + ActionContext
查找顺序:
- 栈顶对象(当前Action实例)
- 模型对象(ModelDriven)
- ValueStack中的其他对象
- Request → Session → Application(域对象)
调试技巧:
<s:debug/>
文件上传与下载实现
单文件上传实现
Action类:
public class FileUploadAction extends ActionSupport {
private File upload;
private String uploadFileName;
private String uploadContentType;
// getter/setter省略
@Override
public String execute() throws Exception {
String savePath = ServletActionContext.getServletContext()
.getRealPath("/uploads");
File destFile = new File(savePath, uploadFileName);
FileUtils.copyFile(upload, destFile);
return SUCCESS;
}
}
JSP表单:
<s:form action="fileUpload" method="post" enctype="multipart/form-data">
<s:file name="upload" label="选择文件"/>
<s:submit value="上传"/>
</s:form>
配置限制:
<constant name="struts.multipart.maxSize" value="10485760"/>
Struts2安全漏洞与防护措施
Q8:S2-045漏洞原理与修复方案
漏洞编号:CVE-2017-5638
原理:Content-Type头未正确过滤,攻击者可注入OGNL表达式执行任意代码
POST /example HTTP/1.1
Content-Type: %{#context['com.opensymphony.xwork2.dispatcher.servletContext'].realPath('/') + 'shell.jsp'}
Content-Length: 1024
修复方案:
- 升级至
2.5.10.1或更高版本
- 配置
struts2.xml:
<constant name="struts.allowed.content.types"
value="application/x-www-form-urlencoded,multipart/form-data,text/xml,application/xml,application/json"/>
- 使用WAF过滤异常Content-Type
Q9:生产环境必备安全配置清单
配置项
建议值
作用
struts.devMode
false
关闭开发模式
struts.allowed.content.types
白名单
过滤Content-Type
struts.excludedPatterns
.*OGNL.*
拒绝OGNL注入
struts.enable.SlashesInActionNames
false
禁用危险路径
🔐 最佳实践:生产环境部署前必须运行struts2-core-checker插件扫描漏洞。
Struts框架版本演进时间轴
2001年 · Struts 1.0发布
Apache Jakarta项目启动,首个MVC框架,奠定Java Web开发标准。
2005年 · Struts 2.0发布
基于WebWork设计,完全重构,支持OGNL与插件机制。
2008年 · Struts 1停止维护
官方宣布停止Struts 1支持,推荐迁移到Struts 2。
2013年 · S2-016漏洞(CVE-2013-2251)
命名空间参数未过滤导致远程代码执行,影响广泛。
2017年 · S2-045漏洞(CVE-2017-5638)
Content-Type头OGNL注入,需紧急升级至2.5.10.1+
2023年11月 · Struts 2.5.32发布
最后稳定版,官方宣布Struts 2进入维护终止(EOL)阶段。
Struts面试高频问题与回答技巧
Q10:如何回答“Struts2的优缺点”这类开放性问题?
推荐回答框架:
✅ 优点(强调工程价值)
- 严格MVC分层,职责清晰,适合大型团队协作
- 拦截器链机制实现权限校验、日志记录等横切逻辑复用
- OGNL表达式简化视图开发,减少JSP脚本
⚠️ 缺点(体现批判性思维)
- 配置复杂(XML冗长),相比Spring Boot开发效率低
- OGNL表达式灵活性带来安全风险(需额外防护)
- 社区活跃度下降,新项目更倾向Spring MVC
💡 建议(展示解决方案)
- 新项目使用Spring Boot + Spring MVC替代
- 旧系统迁移时采用渐进式重构(先解耦Action)
- 通过
struts2-spring-plugin无缝集成Spring