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

Java 基於包結構的請求路由實現:Filter + 反射輕量級 MVC 雛形

     編輯:Java 相關知識

前言

在不引入 Spring MVC 等重型框架的前提下,利用 Servlet Filter 攔截請求,結合約定優於配置的包結構映射與反射調用,可快速構建輕量級請求路由分發器。本文完整展示核心代碼與設計思路。

架構設計

  • 約定:所有 Action 類放在 com.hongyuan 基包下,類名以 Action 結尾 (如 UserAction)。
  • URL 映射:/user/login.do → com.hongyuan.user.LoginAction,方法參數 method=execute 指定調用方法,默認 index。
  • 靜態資源穿透:Filter 優先檢查請求路徑對應的物理文件是否存在,存在則放行 (chain.doFilter)。

核心代碼

1. 基礎 Action 類

package com.hongyuan.route;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public abstract class BaseAction {
    protected ServletRequest request;
    protected ServletResponse response;

    public ServletRequest getRequest() { return request; }
    public void setRequest(ServletRequest request) { this.request = request; }
    public ServletResponse getResponse() { return response; }
    public void setResponse(ServletResponse response) { this.response = response; }
}

2. 請求路由 Filter

package com.hongyuan.route;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;

public class WebFilter implements Filter {
    private static final String BASE_PACKAGE = "com.hongyuan";
    private static final String CLASS_SUFFIX = "Action";

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;

        // 1. 靜態資源直接放行
        if (hasResource(request)) {
            chain.doFilter(req, resp);
            return;
        }

        // 2. 解析請求路徑 -> 類名
        String servletPath = request.getServletPath(); // /user/login.do
        String classPath = servletPath;
        if (classPath.indexOf('.') >= 0) {
            classPath = classPath.substring(0, classPath.indexOf('.')); // /user/login
        }
        if (classPath.endsWith("/")) {
            classPath = classPath.substring(0, classPath.length() - 1);
        }
        String className = BASE_PACKAGE + classPath.replace('/', '.') + CLASS_SUFFIX; // com.hongyuan.user.LoginAction

        // 3. 獲取方法名
        String methodName = request.getParameter("method");
        if (methodName == null || methodName.trim().isEmpty()) {
            methodName = "index";
        }

        // 4. 反射實例化並調用
        try {
            Class<?> clazz = Class.forName(className);
            Object instance = initContext(clazz, request, resp);
            Method method = clazz.getMethod(methodName); // 無參方法
            Object result = method.invoke(instance);
            resp.getWriter().print(result); // 直接輸出返回值 (JSON/HTML/文本)
        } catch (Exception e) {
            e.printStackTrace(resp.getWriter()); // 錯誤信息輸出到前端
        }
    }

    private boolean hasResource(HttpServletRequest request) {
        String realPath = request.getServletContext().getRealPath(request.getServletPath());
        return new File(realPath).exists();
    }

    private Object initContext(Class<?> clazz, ServletRequest req, ServletResponse resp) throws Exception {
        Object instance = clazz.getDeclaredConstructor().newInstance();
        clazz.getMethod("setRequest", ServletRequest.class).invoke(instance, req);
        clazz.getMethod("setResponse", ServletResponse.class).invoke(instance, resp);
        return instance;
    }

    @Override public void init(FilterConfig config) {}
    @Override public void destroy() {}
}

3. 具體 Action 示例

package com.hongyuan.user;

import com.hongyuan.route.BaseAction;

public class LoginAction extends BaseAction {
    public String execute() {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        // 業務邏輯...
        return "{\"success\": true, \"msg\": \"Welcome " + username + "\"}";
    }

    public String index() {
        return "login page html...";
    }
}

4. web.xml 配置

<filter>
    <filter-name>webFilter</filter-name>
    <filter-class>com.hongyuan.route.WebFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>webFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

關鍵設計點解析

  1. 單例 vs 原型:示例中每請求 newInstance() 創建新 Action 實例 (原型模式),線程安全、無狀態污染。若 Action 無狀態可改為單例緩存 (ConcurrentHashMap)。
  2. 方法簽名限制:getMethod(methodName) 僅支援無參方法。擴展支援參數綁定需引入參數解析器 (如 Spring 的 HandlerMethodArgumentResolver)。
  3. 異常處理:捕獲所有反射異常並輸出堆疊跡到響應,開發調試友好;生產環境應統一錯誤頁面並記錄日誌。
  4. 編碼問題:建議在 Filter 鏈首部添加 CharacterEncodingFilter 強制 UTF-8。
  5. 安全性:包結構映射天然防止任意類調用,但需確保 BASE_PACKAGE 不暴露敏感內部類。

擴展方向

  • 註解路由:引入 @RequestMapping 註解替代包結構約定,靈活度更高。
  • 返回值處理器:識別 String (視圖名)、ModelAndView、@ResponseBody 對象等多種返回類型。
  • 攔截器鏈:在 Filter 內部構建 HandlerInterceptor 鏈支援預/後處理 (權限、日誌、事務)。
  • 參數綁定:利用 Method.getParameters() 獲取參數名 (需編譯參數 -parameters) 實現請求參數自動映射。

總結

基於包結構的請求路由是理解 MVC 框架核心機制的最佳切入點。通過 Filter 攔截 + 反射調用 + 約定映射,僅 100 行核心代碼即可實現一個功能可用的微型 MVC。這不僅適合中小項目快速落地,更能幫助開發者深入理解 Spring MVC、Struts2 等主流框架的底層原理。

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