WebWork 2 最大創新在於 **徹底解耦 Servlet API**,將 HTTP 請求轉為純 Java Map 傳入 XWork 核心層。本文分析 ServletDispatcher 請求轉換與 ParametersInterceptor 屬性綁定機制。
public static HashMap createContextMap(Map requestMap, Map parameterMap,
Map sessionMap, Map applicationMap,
HttpServletRequest request, HttpServletResponse response,
ServletConfig servletConfig) {
HashMap extraContext = new HashMap();
extraContext.put(ActionContext.PARAMETERS, parameterMap);
extraContext.put(ActionContext.SESSION, sessionMap);
extraContext.put(ActionContext.APPLICATION, applicationMap);
extraContext.put(ActionContext.LOCALE, (locale == null) ? request.getLocale() : locale);
extraContext.put(HTTP_REQUEST, request); // 保留原始請求 (少數需直接存取)
extraContext.put(HTTP_RESPONSE, response);
extraContext.put(SERVLET_CONFIG, servletConfig);
extraContext.put(ComponentInterceptor.COMPONENT_MANAGER,
request.getAttribute(ComponentManager.COMPONENT_MANAGER_KEY));
// 便利鍵值,供 OGNL 直接存取
extraContext.put("request", requestMap);
extraContext.put("session", sessionMap);
extraContext.put("application", applicationMap);
extraContext.put("parameters", parameterMap);
extraContext.put("attr", new AttributeMap(extraContext)); // 統一存取 request/session/application
return extraContext;
}
public void serviceAction(HttpServletRequest request, HttpServletResponse response,
String namespace, String actionName,
Map requestMap, Map parameterMap, Map sessionMap, Map applicationMap) {
HashMap extraContext = createContextMap(...);
// 值棧傳遞 (支援攔截器間共享)
OgnlValueStack stack = (OgnlValueStack) request.getAttribute(ServletActionContext.WEBWORK_VALUESTACK_KEY);
if (stack != null) {
extraContext.put(ActionContext.VALUE_STACK, new OgnlValueStack(stack)); // 複製棧
}
try {
// 核心委派:ActionProxyFactory → ActionProxy → ActionInvocation
ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy(namespace, actionName, extraContext);
request.setAttribute(ServletActionContext.WEBWORK_VALUESTACK_KEY, proxy.getInvocation().getStack());
proxy.execute(); // 執行攔截器鏈 + Action + Result
} catch (ConfigurationException e) {
sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);
} catch (Exception e) {
sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
} finally {
// 還原原始值棧
if (stack != null) request.setAttribute(ServletActionContext.WEBWORK_VALUESTACK_KEY, stack);
}
}
ActionProxy 介面定義 execute(), getInvocation(), getConfig()。DefaultActionProxy 實作:持有 ActionInvocation、ActionConfig、executeResult 標誌。ActionProxyFactory 抽象工廠,DefaultActionProxyFactory 實作建立 Proxy 與 Invocation。public class ParametersInterceptor extends AroundInterceptor {
protected void before(ActionInvocation invocation) throws Exception {
// 1. 排除標記為 NoParameters 的 Action
if (invocation.getAction() instanceof NoParameters) return;
// 2. 取得參數 Map
Map parameters = ActionContext.getContext().getParameters();
if (parameters == null) return;
// 3. 設定 OGNL 運作上下文旗標
ActionContext ctx = invocation.getInvocationContext();
ctx.put(InstantiatingNullHandler.CREATE_NULL_OBJECTS, Boolean.TRUE); // 自動建立巢狀物件
ctx.put(XWorkMethodAccessor.DENY_METHOD_EXECUTION, Boolean.TRUE); // 禁止任意方法執行
ctx.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE); // 收集類型轉換錯誤
try {
OgnlValueStack stack = ActionContext.getContext().getValueStack();
for (Map.Entry entry : parameters.entrySet()) {
String name = entry.getKey().toString();
// 4. 安全過濾:排除 OGNL 特殊字元 (= , #) 防止注入
if (acceptableName(name)) {
Object value = entry.getValue();
stack.setValue(name, value); // OGNL 設值:自動類型轉換、巢狀屬性建立
}
}
} finally {
// 5. 還原旗標
ctx.put(InstantiatingNullHandler.CREATE_NULL_OBJECTS, Boolean.FALSE);
ctx.put(XWorkMethodAccessor.DENY_METHOD_EXECUTION, Boolean.FALSE);
ctx.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.FALSE);
}
}
protected boolean acceptableName(String name) {
return name.indexOf('=') == -1 && name.indexOf(',') == -1 && name.indexOf('#') == -1;
}
protected void after(ActionInvocation dispatcher, String result) {}
}
| 特性 | 實現方式 | 優勢 |
|---|---|---|
| Servlet 解耦 | ServletDispatcher 將 request/session/parameters 轉為 Map | 核心層可單元測試、移植非 Servlet 環境 (Portlet, CLI, Test) |
| OGNL 統一存取 | ValueStack + OGNL 表達式 | 單一語法存取 Action、Model、Request、Session、Application |
| 類型轉換集中化 | XWorkConverter + 自訂 TypeConverter | 字串→日期、枚舉、集合、自訂物件 統一處理 |
| 安全防護 | ParametersInterceptor 過濾 OGNL 特殊字元、DENY_METHOD_EXECUTION | 防止 OGNL 注入攻擊 (如 Struts 2 歷史漏洞根源) |
| 自動物件建立 | InstantiatingNullHandler | user.address.city 自動 new Address() 無需手動初始化 |