傳統 Servlet 開發需在 web.xml 逐一對應 URL 與類別。本範例展示一種基於「套件命名慣例」的自動路由機制:透過 Filter 攔截請求,依 URL 路徑動態推導處理類別與方法,實現零配置的請求分派。
javax.servlet.Filter,攔截所有請求並執行路由邏輯ServletRequest/ServletResponse 的注入與存取基礎套件 + URL路徑轉換 + "Action" 後綴 定位處理類別WebFilterpublic class WebFilter implements Filter {
public static final String BASE_PACKAGE = "com.hongyuan";
public static final String CLASS_FLAG = "Action";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (!hasResouce(request)) { // 靜態資源直接放行
String requestPath = ((HttpServletRequest) request).getServletPath();
// 移除副檔名與尾端斜線
if (requestPath.indexOf(".") >= 0) {
requestPath = requestPath.substring(0, requestPath.indexOf("."));
}
if (requestPath.endsWith("/")) {
requestPath = requestPath.substring(0, requestPath.length() - 1);
}
// 組裝完整類別名稱:com.hongyuan.模組.功能Action
String className = BASE_PACKAGE + requestPath.replaceAll("/", ".") + CLASS_FLAG;
String methodName = request.getParameter("method");
if (methodName == null || "".equals(methodName.trim())) {
methodName = "index"; // 預設方法
}
try {
Class> clazz = Class.forName(className);
Object instance = initContext(clazz, request, response);
Method method = clazz.getMethod(methodName, new Class[]{});
Object result = method.invoke(instance, new Object[]{});
response.getWriter().print(result);
} catch (Exception e) {
e.printStackTrace(response.getWriter());
}
} else {
chain.doFilter(request, response); // 靜態資源交由預設 Servlet 處理
}
}
private boolean hasResouce(ServletRequest request) {
String realPath = request.getServletContext().getRealPath(
((HttpServletRequest) request).getServletPath());
return new File(realPath).exists();
}
private Object initContext(Class> clazz, ServletRequest request, ServletResponse response)
throws Exception {
Object instance = clazz.newInstance();
clazz.getMethod("setRequest", ServletRequest.class).invoke(instance, request);
clazz.getMethod("setResponse", ServletResponse.class).invoke(instance, response);
return instance;
}
// init/destroy 略
}
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; }
}
假設專案結構如下:
com.hongyuan
├── user
│ └── UserAction.java // 對應 /user/user.do
└── order
└── OrderAction.java // 對應 /order/order.do
UserAction 實作:
package com.hongyuan.user;
public class UserAction extends BaseAction {
public String index() {
return "用戶首頁";
}
public String login() {
String username = request.getParameter("username");
return "歡迎 " + username;
}
}
請求 /user/user.do?method=login&username=Tom 將自動路由至 UserAction.login()。
| 優點 | 缺點 |
|---|---|
| 零 XML 配置,依慣例自動路由 | 類別命名需嚴格遵循規範 |
| 新增功能只需建立對應 Action 類別 | 反射調用效能略低於直接調用 |
| 集中式例外處理與前置/後置邏輯 | 靜態資源判斷邏輯較簡單 |
/user/123 映射至 getById(123))Class 與 Method 物件提升效能此路由模式適用中小型專案,能大幅減少設定檔維護成本。核心在於「慣例優於配置」,配合反射機制實現動態分派,是理解框架底層運作的良好切入點。