在不引入 Spring MVC 等重型框架的前提下,利用 Servlet Filter 攔截請求,結合約定優於配置的包結構映射與反射調用,可快速構建輕量級請求路由分發器。本文完整展示核心代碼與設計思路。
com.hongyuan 基包下,類名以 Action 結尾 (如 UserAction)。/user/login.do → com.hongyuan.user.LoginAction,方法參數 method=execute 指定調用方法,默認 index。chain.doFilter)。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; }
}
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() {}
}
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...";
}
}
<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>
newInstance() 創建新 Action 實例 (原型模式),線程安全、無狀態污染。若 Action 無狀態可改為單例緩存 (ConcurrentHashMap)。getMethod(methodName) 僅支援無參方法。擴展支援參數綁定需引入參數解析器 (如 Spring 的 HandlerMethodArgumentResolver)。BASE_PACKAGE 不暴露敏感內部類。@RequestMapping 註解替代包結構約定,靈活度更高。String (視圖名)、ModelAndView、@ResponseBody 對象等多種返回類型。HandlerInterceptor 鏈支援預/後處理 (權限、日誌、事務)。Method.getParameters() 獲取參數名 (需編譯參數 -parameters) 實現請求參數自動映射。基於包結構的請求路由是理解 MVC 框架核心機制的最佳切入點。通過 Filter 攔截 + 反射調用 + 約定映射,僅 100 行核心代碼即可實現一個功能可用的微型 MVC。這不僅適合中小項目快速落地,更能幫助開發者深入理解 Spring MVC、Struts2 等主流框架的底層原理。