Struts 1 時代用 Servlet 做控制器,Struts 2 升級為 Filter + 攔截器鏈。本文通過三個版本迭代,手寫實現核心控制器邏輯:v1.0 硬編碼 if-else → v2.0 Map+接口多態 → v3.0 XML配置+DOM4J解析+反射實例化,還原框架演進本質,掌握 MVC 核心調度機制。
| 維度 | Struts 1 | Struts 2 |
|---|---|---|
| 控制器類型 | Servlet (ActionServlet) | Filter (StrutsPrepareAndExecuteFilter) |
| 攔截粒度 | 僅 *.do 請求 | 可配置 /* 全路徑攔截 |
| 線程模型 | 單例 Action,線程不安全 | 每請求新建 Action,線程安全 |
| 表單數據綁定 | ActionForm 手動綁定 | OGNL 表達式自動綁定 |
| 視圖解析 | JSP 為主 | 多視圖技術 (JSP/FreeMarker/Velocity/JSON) |
package com.cdv.web.filter;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
public class StrutsPreparedAndExecuteFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String uri = request.getRequestURI(); // /context/login.do
boolean isDo = uri.endsWith(".do");
if (isDo) {
// 提取操作名:/login.do → login
String op = uri.substring(uri.lastIndexOf("/") + 1, uri.lastIndexOf("."));
if ("login".equals(op)) {
request.setAttribute("username", "zhangsan");
request.getRequestDispatcher("/success.jsp").forward(request, response);
} else {
// 其他操作...
chain.doFilter(request, response);
}
} else {
chain.doFilter(request, response); // 靜態資源放行
}
}
@Override public void init(FilterConfig config) throws ServletException {}
@Override public void destroy() {}
}
痛點:操作增多導致 if-else 膨脹、違反開閉原則、難以擴展維護。
// 統一 Action 接口
package com.cdv.web.action;
import javax.servlet.http.HttpServletRequest;
public interface Action {
String execute(HttpServletRequest request);
}
// 具體業務 Action
package com.cdv.web.action;
import javax.servlet.http.HttpServletRequest;
public class LoginAction implements Action {
@Override
public String execute(HttpServletRequest request) {
String username = request.getParameter("username");
if ("zhangsan".equals(username)) {
return "success"; // 邏輯視圖名
}
return "error";
}
}
// 核心 Filter:Map 註冊 + 反射創建 (簡化版,new 實例)
package com.cdv.web.filter;
import com.cdv.web.action.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class StrutsPreparedAndExecuteFilter2 implements Filter {
private Map actionMap = new HashMap<>();
@Override
public void init(FilterConfig config) throws ServletException {
// 硬編碼註冊 (後續將移至 XML)
actionMap.put("login", new LoginAction());
// actionMap.put("logout", new LogoutAction());
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String uri = request.getRequestURI();
if (uri.endsWith(".do")) {
String op = uri.substring(uri.lastIndexOf("/") + 1, uri.lastIndexOf("."));
Action action = actionMap.get(op);
if (action != null) {
String result = action.execute(request);
if ("success".equals(result)) {
request.setAttribute("user", "zhangsan");
request.getRequestDispatcher("/success.jsp").forward(request, response);
} else {
request.getRequestDispatcher("/error.jsp").forward(request, response);
}
} else {
chain.doFilter(request, response);
}
} else {
chain.doFilter(request, response);
}
}
@Override public void destroy() {}
}
進步:消除 if-else、引入接口多態、Map 快速查找、單一職責分離。
殘留:註冊仍硬編碼、Action 實例化耦合具體類、無配置文件外部化。
<?xml version="1.0" encoding="UTF-8"?>
<package name="mypackage">
<action name="loginAction" class="com.cdv.web.action.LoginAction"></action>
<action name="logoutAction" class="com.cdv.web.action.LogoutAction"></action>
</package>
package com.cdv.web.filter;
import com.cdv.web.action.Action;
import org.dom4j.Document;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class StrutsPreparedAndExecuteFilter3 implements Filter {
private Map actionMap = new HashMap<>();
@Override
public void init(FilterConfig config) throws ServletException {
try {
// 1. 讀取配置文件 (假設在 classpath 根目錄)
SAXReader reader = new SAXReader();
Document document = reader.read(getClass().getClassLoader().getResourceAsStream("struts.xml"));
// 2. XPath 解析 action 節點
List actionNodes = document.selectNodes("//action");
for (Node node : actionNodes) {
// 3. 提取屬性
String name = node.valueOf("@name"); // loginAction
String className = node.valueOf("@class"); // com.cdv.web.action.LoginAction
// 4. 反射實例化
Class> clazz = Class.forName(className);
Action action = (Action) clazz.newInstance(); // 需無參構造器
// 5. 註冊到 Map
actionMap.put(name, action);
}
} catch (Exception e) {
throw new ServletException("Struts 配置初始化失敗", e);
}
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String uri = request.getRequestURI(); // /context/loginAction.do
if (uri.endsWith(".do")) {
// 提取 action 名:/loginAction.do → loginAction
String actionName = uri.substring(uri.lastIndexOf("/") + 1, uri.lastIndexOf("."));
Action action = actionMap.get(actionName);
if (action != null) {
String result = action.execute(request);
// 簡化:結果直接對應 JSP 頁面
request.getRequestDispatcher("/" + result + ".jsp").forward(request, response);
} else {
chain.doFilter(request, response);
}
} else {
chain.doFilter(request, response);
}
}
@Override public void destroy() {}
}
| 版本 | 核心模式 | 解決問題 |
|---|---|---|
| v1.0 | 前端控制器 | 統一請求入口、公共預處理 |
| v2.0 | 命令模式 + 工廠模式 (Map註冊) | 消除條件分支、業務邏輯解耦、動態分發 |
| v3.0 | 配置元數據 + 反射工廠 + 模板方法 | 配置外部化、零代碼擴展、聲明式編程 |
ActionForward/Result 對象,包含類型 (forward/redirect/stream/json)、路徑、參數,由 ResultProcessor 統一處理Interceptor 接口 (pre/invoke/post),InterceptorChain 負責鏈式調用,實現日志、事務、權限、參數準備等橫切關注點ValueStack (OGNL 表達式根對象),存放 Action 實例、請求參數、Session、Application,視圖層統一訪問TypeConverter、ValidationInterceptor、註解/Xml 驗證規則package extends、include、命名空間隔離Plugin 接口 (init/destroy),支持 Spring/Hibernate/JSON/REST 插件熱插拔ExceptionMappingInterceptor、友好錯誤頁從硬編碼 Servlet 到可配置 Filter,三個版本迭代演示了框架設計的核心演進邏輯:統一入口 → 接口抽象 → 配置外部化 → 反射實例化 → 聲明式編程。手寫核心調度鏈路,不僅能深度理解 Struts 內部機制,更能掌握「框架即庫 + 配置元數據 + 擴展點」的通用設計哲學。建議開發者在理解基礎上,嘗試補全攔截器鏈、值棧、結果處理等模塊,構建屬於自己的微型 MVC 框架,將框架使用者蛻變為框架設計者。