基於 WebWork 發展的 MVC 框架,核心特點:POJO Action、攔截器鏈、OGNL 表達式、結果類型多樣。Struts 1 已停止維護,現代專案建議 Spring MVC / Spring Boot。
用戶請求
↓
[過濾器: StrutsPrepareAndExecuteFilter]
↓
[ActionMapping] → 根據 URL 找 Action 配置
↓
[ActionProxy] → 建立 ActionInvocation
↓
[攔截器鏈] → 按順序執行 18 個預設攔截器
├─ 1. ServletConfigInterceptor
├─ 2. ParametersInterceptor (參數注入)
├─ 3. ConversionErrorInterceptor
├─ 4. PrepareInterceptor (Preparable)
├─ 5. ModelDrivenInterceptor
├─ 6. ... (共 18 個,defaultStack)
↓
[Action 實例] → 執行 execute()/自定義方法
↓
[結果處理] → Result 類型 (dispatcher/redirect/stream/json...)
↓
響應返回
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
關鍵點:StrutsPrepareAndExecuteFilter 合併了舊版的 PrepareFilter + ExecuteFilter,負責配置載入與請求執行。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<!-- 常量配置 -->
<constant name="struts.devMode" value="true"/>
<constant name="struts.i18n.encoding" value="UTF-8"/>
<constant name="struts.objectFactory" value="spring"/> <!-- 整合 Spring -->
<!-- 包繼承:繼承 struts-default 獲得預設攔截器、結果類型 -->
<package name="default" namespace="/" extends="struts-default">
<!-- Action 配置 -->
<action name="login" class="com.example.action.LoginAction" method="execute">
<result name="success" type="dispatcher">/welcome.jsp</result>
<result name="input">/login.jsp</result>
<result name="error" type="redirect">/error.html</result>
</action>
<!-- 通配符映射 -->
<action name="user/*" class="com.example.action.UserAction" method="{1}">
<result name="list">/user/list.jsp</result>
<result name="edit">/user/edit.jsp</result>
</action>
</package>
</struts>
public class LoginAction {
private String username;
private String password;
private UserService userService; // Spring 注入
public String execute() {
User user = userService.login(username, password);
if (user != null) {
return "success";
}
addActionError("帳號或密碼錯誤");
return "input";
}
// getter/setter
}
public class LoginAction implements Action {
public String execute() throws Exception { ... }
}
public class UserAction implements ModelDriven<User> {
private User user = new User();
public User getModel() { return user; }
public String save() { userService.save(user); return "success"; }
}
<interceptor-stack name="defaultStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
<interceptor-ref name="servletConfig"/>
<interceptor-ref name="i18n"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="chain"/>
<interceptor-ref name="modelDriven"/>
<interceptor-ref name="fileUpload"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="datetime"/>
<interceptor-ref name="multiselect"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="actionMappingParams"/>
<interceptor-ref name="params"> <!-- 核心:參數注入 -->
<param name="excludeParams">dojo\..*,^struts\..*,^session\..*,^request\..*,^application\..*,^servlet(Request|Response)\..*,parameters\..*</param>
</interceptor-ref>
<interceptor-ref name="conversionError"/>
<interceptor-ref name="validation"> <!-- 驗證 -->
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="workflow"> <!-- 驗證失敗返回 input -->
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="debugging"/>
</interceptor-stack>
public class AuthInterceptor extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation invocation) throws Exception {
Map<String, Object> session = invocation.getInvocationContext().getSession();
if (session.get("user") == null) {
return "login"; // 重定向登入
}
return invocation.invoke(); // 繼續執行
}
}
<interceptor name="auth" class="com.example.interceptor.AuthInterceptor"/>
<interceptor-stack name="secureStack">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="auth"/>
</interceptor-stack>
<action name="admin/*" class="...">
<interceptor-ref name="secureStack"/>
...
</action>
| 類型 | 類別 | 用途 |
|---|---|---|
| dispatcher (預設) | ServletDispatcherResult | 轉發到 JSP/HTML |
| redirect | ServletRedirectResult | 重定向 (瀏覽器地址變更) |
| redirectAction | ServletActionRedirectResult | 重定向到另一個 Action |
| stream | StreamResult | 檔案下載、圖片輸出 |
| json | JSONResult | REST API 返回 JSON (需 json-plugin) |
| freemarker/velocity | FreeMarkerResult/VelocityResult | 模板引擎渲染 |
| tiles | TilesResult | Apache Tiles 佈局 |
user.name、user.address.cityusers.{name} (投影)、users.?[age > 18] (過濾)、users.^[age > 18] (首個匹配)、users.$[age > 18] (最後匹配)#session['key']、#request.key、#parameters.iduser.getFullName()、@com.example.Util@formatDate(date) (靜態方法)+ - * / %、== != < > <= >=、and or not、in (集合包含)| 問題 | 原因 | 解決 |
|---|---|---|
| 參數無法注入 | 1) 無 getter/setter 2) 類型轉換失敗 3) excludeParams 攔截 | 檢查屬性封裝、自訂 TypeConverter、調整 params.interceptor excludeParams |
| 檔案上傳失效 | 缺少 fileUpload 攔截器、表單未設 enctype=multipart/form-data、超過最大尺寸 | 確認 struts.multipart.maxSize、commons-fileupload 依賴 |
| 驗證不生效 | 未實作 Validateable、方法名在 excludeMethods、驗證 XML 命名不符 | Action 類同名 -validation.xml、@Validations 註解 |
| 中文亂碼 | 未設 struts.i18n.encoding、Tomcat URIEncoding、Filter 順序 | 常量設 UTF-8、web.xml CharacterEncodingFilter 在最前 |
| 攔截器不執行 | 攔截器棧引用錯誤、package 未 extends struts-default | 核對 interceptor-ref name、package extends |
| Struts 2 | Spring MVC |
|---|---|
| Action 類 | @Controller / @RestController |
| struts.xml | @RequestMapping / 註解配置 |
| 攔截器 | HandlerInterceptor / Filter |
| OGNL | SpEL / @RequestParam / @PathVariable |
| Result 類型 | ViewResolver / @ResponseBody / ResponseEntity |
| 驗證 | Bean Validation (JSR-380) + @Valid |
| 文件上傳 | MultipartFile + StandardServletMultipartResolver |
| 國際化 | MessageSource + LocaleResolver |
Struts 2 以攔截器鏈、OGNL、POJO Action 為核心特色,曾風靡一時。但因安全漏洞頻發 (OGNL 注入)、社區活躍度下降、功能被 Spring MVC 完整覆蓋並超越,**新專案強烈建議直接使用 Spring Boot + Spring MVC**。維護舊專案時,掌握上述核心流程與配置可快速定位問題。