首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

手寫 Struts 框架核心原理:從 Servlet 到 Filter,從硬編碼到 XML+反射的演進之路

     編輯:Java 綜合教學

前言

Struts 1 時代用 Servlet 做控制器,Struts 2 升級為 Filter + 攔截器鏈。本文通過三個版本迭代,手寫實現核心控制器邏輯:v1.0 硬編碼 if-else → v2.0 Map+接口多態 → v3.0 XML配置+DOM4J解析+反射實例化,還原框架演進本質,掌握 MVC 核心調度機制。

一、Struts 1 vs Struts 2 核心差異

維度Struts 1Struts 2
控制器類型Servlet (ActionServlet)Filter (StrutsPrepareAndExecuteFilter)
攔截粒度僅 *.do 請求可配置 /* 全路徑攔截
線程模型單例 Action,線程不安全每請求新建 Action,線程安全
表單數據綁定ActionForm 手動綁定OGNL 表達式自動綁定
視圖解析JSP 為主多視圖技術 (JSP/FreeMarker/Velocity/JSON)

二、版本演進實戰

v1.0:硬編碼 if-else (Struts 1 風格)

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 膨脹、違反開閉原則、難以擴展維護。

v2.0:Map 註冊 + 接口多態 (命令模式雛形)

// 統一 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 實例化耦合具體類、無配置文件外部化。

v3.0:XML 配置 + DOM4J 解析 + 反射實例化 (Struts 2 核心雛形)

struts.xml 配置文件

<?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>

核心 Filter 實現

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配置元數據 + 反射工廠 + 模板方法配置外部化、零代碼擴展、聲明式編程

四、向生產級框架演進的關鍵補強

  1. 結果視圖解耦:Action 返回 ActionForward/Result 對象,包含類型 (forward/redirect/stream/json)、路徑、參數,由 ResultProcessor 統一處理
  2. 攔截器鏈:Interceptor 接口 (pre/invoke/post),InterceptorChain 負責鏈式調用,實現日志、事務、權限、參數準備等橫切關注點
  3. 值棧/上下文:ValueStack (OGNL 表達式根對象),存放 Action 實例、請求參數、Session、Application,視圖層統一訪問
  4. 類型轉換/驗證:TypeConverter、ValidationInterceptor、註解/Xml 驗證規則
  5. 配置繼承/模塊化:package extends、include、命名空間隔離
  6. 插件機制:Plugin 接口 (init/destroy),支持 Spring/Hibernate/JSON/REST 插件熱插拔
  7. 異常處理:全局異常映射、ExceptionMappingInterceptor、友好錯誤頁

五、結語

從硬編碼 Servlet 到可配置 Filter,三個版本迭代演示了框架設計的核心演進邏輯:統一入口 → 接口抽象 → 配置外部化 → 反射實例化 → 聲明式編程。手寫核心調度鏈路,不僅能深度理解 Struts 內部機制,更能掌握「框架即庫 + 配置元數據 + 擴展點」的通用設計哲學。建議開發者在理解基礎上,嘗試補全攔截器鏈、值棧、結果處理等模塊,構建屬於自己的微型 MVC 框架,將框架使用者蛻變為框架設計者。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved