早期 Servlet 時代,開發者在 out.println() 中拼接 HTML,業務邏輯與視圖渲染強耦合,維護極度困難。JSP 雖分離了表現,但大量 Scriptlet (<% %>) 導致控制流程散落在視圖中。MVC (Model-View-Controller) 模式透過職責分離解決此問題,Struts 1 是 Java Web 史上最成功的 MVC 框架之一,其設計哲學深遠影響後續框架。
| 角色 | 職責 | Struts 對應組件 | 技術實現 |
|---|---|---|---|
| Model | 業務邏輯、資料存取、狀態管理 | JavaBean、EJB、Hibernate/JPA、Service 層 | POJO、DAO、Service、Domain Object |
| View | 資料呈現、用戶互動介面 | JSP + Struts Taglib、JSTL、EL | HTML、CSS、JavaScript、模板引擎 |
| Controller | 請求接收、流程控制、視圖分派 | ActionServlet (核心)、Action、ActionForm、ActionMapping | Front Controller 模式、命令模式 |
用戶請求 (HTTP)
│
▼
ActionServlet (web.xml 配置的 Front Controller)
│
├─▶ 解析請求路徑 → 查找 ActionMapping
│
├─▶ 建立/取得 ActionForm → 自動填充表單資料 → 驗證 (validate)
│
├─▶ 實例化 Action → 執行 execute() → 業務邏輯處理
│
├─▶ Action 返回 ActionForward (success/failure/自訂)
│
▼
ActionServlet 根據 Forward 轉發至目標 View (JSP/Tiles)
│
▼
View 渲染回應 → 返回用戶瀏覽器
web.xml 中配置為啟動載入 (load-on-startup)struts-config.xml 初始化 ActionMapping、FormBean、Forward、DataSource、PlugIn 等public class LoginForm extends ActionForm {
private String username;
private String password;
// getter/setter 省略
@Override
public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) {
ActionErrors errors = new ActionErrors();
if (username == null || username.trim().isEmpty()) {
errors.add("username", new ActionError("error.username.required"));
}
if (password == null || password.length() < 6) {
errors.add("password", new ActionError("error.password.minlength", "6"));
}
return errors; // 空集合或 null 表示驗證通過
}
@Override
public void reset(ActionMapping mapping, HttpServletRequest request) {
username = null;
password = null; // 重置狀態,避免殘留資料
}
}
關鍵點:屬性名對應表單欄位名;validate() 回傳 ActionErrors;reset() 清理 Session 範圍 Form 的殘留值。
public class LoginAction extends Action {
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
LoginForm loginForm = (LoginForm) form;
UserService userService = ServiceLocator.getUserService(); // 取得業務服務
try {
User user = userService.authenticate(loginForm.getUsername(), loginForm.getPassword());
if (user != null) {
request.getSession().setAttribute("currentUser", user);
return mapping.findForward("success");
} else {
ActionErrors errors = new ActionErrors();
errors.add("login", new ActionError("error.login.failed"));
saveErrors(request, errors);
return mapping.findForward("failure");
}
} catch (ServiceException e) {
log.error("登入服務異常", e);
return mapping.findForward("systemError");
}
}
}
<struts-config>
<!-- Form Bean 定義 -->
<form-beans>
<form-bean name="loginForm" type="com.app.struts.LoginForm"/>
</form-beans>
<!-- 全域 Forward -->
<global-forwards>
<forward name="login" path="/login.jsp"/>
<forward name="welcome" path="/welcome.jsp"/>
</global-forwards>
<!-- Action 映射 -->
<action-mappings>
<!-- 直接轉發到登入頁 -->
<action path="/login" forward="/login.jsp"/>
<!-- 處理登入提交 -->
<action path="/doLogin"
type="com.app.struts.LoginAction"
name="loginForm"
scope="request"
validate="true"
input="/login.jsp"> <!-- 驗證失敗返回頁面 -->
<forward name="success" path="/welcome.do"/> <-- 轉發到另一 Action -->
<forward name="failure" path="/login.jsp"/>
<forward name="systemError" path="/error.jsp"/>
</action>
<!-- 歡迎頁 Action (可做額外邏輯) -->
<action path="/welcome"
forward="/welcome.jsp"
name="loginForm"
scope="request"/>
</action-mappings>
<!-- 國際化資源 -->
<message-resources parameter="ApplicationResources"/>
<!-- Tiles 整合 -->
<plug-in className="org.apache.struts.tiles.TilesPlugin">
<set-property property="definitions-config" value="/WEB-INF/tiles-defs.xml"/>
</plug-in>
</struts-config>
src/
├── com/app/struts/
│ ├── LoginForm.java
│ ├── LoginAction.java
│ └── User.java (Model)
├── ApplicationResources.properties
└── ApplicationResources_zh_TW.properties
WebContent/
├── WEB-INF/
│ ├── struts-config.xml
│ ├── web.xml
│ └── tiles-defs.xml
├── login.jsp
└── welcome.jsp
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<html:html locale="true">
<head><title><bean:message key="login.title"/></title></head>
<body>
<html:errors header="<div class='error'>" footer="</div>"/>
<html:form action="/doLogin">
<table>
<tr>
<td><bean:message key="label.username"/>:</td>
<td><html:text property="username" size="20"/></td>
</tr>
<tr>
<td><bean:message key="label.password"/>:</td>
<td><html:password property="password" size="20"/></td>
</tr>
<tr>
<td colspan="2" align="center">
<html:submit value="<bean:message key='btn.login'/>"/>
</td>
</tr>
</table>
</html:form>
</body>
</html:html>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<html>
<body>
<h1><bean:message key="welcome.heading"/>,
<bean:write name="loginForm" property="username" filter="true"/>!</h1>
<p><bean:message key="welcome.desc"/></p>
</body>
</html>
login.title=系統登入
label.username=用戶名
label.password=密碼
btn.login=登入
error.username.required=請輸入用戶名
error.password.minlength=密碼長度至少 {0} 字元
error.login.failed=用戶名或密碼錯誤
welcome.heading=歡迎
welcome.desc=您已成功登入系統
參考 Struts Taglib 指南 的 Tiles 章節,實現 Header/Menu/Body/Footer 重用佈局,ActionForward 指向 Tiles 定義名而非具體 JSP。
<!-- validator-rules.xml 內建規則 -->
<form-validation>
<formset>
<form name="loginForm">
<field property="username" depends="required">
<msg name="required" key="error.username.required"/>
</field>
<field property="password" depends="required,minlength">
<msg name="minlength" key="error.password.minlength"/>
<arg0 key="label.password"/>
<var><var-name>minlength</var-name><var-value>6</var-value></var>
</field>
</form>
</formset>
</form-validation>
ActionForm 繼承 ValidatorForm,struts-config.xml 配置 ValidatorPlugIn,即可移除 Java 驗證代碼。
<global-exceptions>
<exception type="com.app.exception.BusinessException"
handler="com.app.exception.BusinessExceptionHandler"
key="error.business"
path="/error.jsp"/>
<exception type="java.lang.Exception"
handler="com.app.exception.SystemExceptionHandler"
key="error.system"
path="/error.jsp"/>
</global-exceptions>
| Struts 1 概念 | Spring MVC 對應 | 關鍵差異 |
|---|---|---|
| ActionServlet | DispatcherServlet | Spring 更輕量、支援 REST、非侵入式 |
| ActionForm | @ModelAttribute / Command Object | POJO 即可,無需繼承基類 |
| Action | @Controller + @RequestMapping | 方法級映射、支援多種返回類型 |
| struts-config.xml | Java Config / 註解 | 類型安全、IDE 重構友善 |
| Tiles | Thymeleaf / FreeMarker / JSP + Tiles 3 | 模板引擎功能更強 |
| Validator | Bean Validation (JSR-380) + @Valid | 標準化、聲明式、跨層複用 |
Struts 1 以 ActionServlet 為核心,配合 ActionForm、Action、struts-config.xml 實現經典 MVC 模式。其標籤庫、Tiles、Validator、國際化、異常處理等機制構建了完整的企業級 Web 開發框架。雖已停止維護,但理解其架構設計(Front Controller、命令模式、聲明式配置)對掌握 Spring MVC、Spring Boot 等現代框架極有幫助。存量系統建議採用漸進式遷移:新模組用 Spring Boot,舊模組穩定運行,透過 API 或資料庫整合共存。