在 Struts2 應用中,區分 AJAX 請求與普通表單提交、針對 AJAX 回傳 JSON、以及利用內建攔截器進行效能剖析,是提升開發效率與系統可觀測性的關鍵技能。本文詳細說明基於 Header 的 AJAX 判斷邏輯、基類封裝策略,以及 Profiling 攔截器的啟用與實戰應用。
| 特性 | 普通表單提交 | AJAX 請求 |
|---|---|---|
| 發起者 | 瀏覽器原生表單 | JavaScript (XMLHttpRequest / fetch / jQuery.ajax) |
| 期望回應 | 完整 HTML 頁面 | 資料片段(JSON、XML、HTML 片段) |
| 頁面行為 | 整頁跳轉/重新載入 | 局部更新,無感刷新 |
X-Requested-WithjQuery 等主流 JS 庫在發送 AJAX 時,會自動加入請求頭:
X-Requested-With: XMLHttpRequest
伺服器端檢測該 Header 即可可靠判斷(前提是前端統一使用 jQuery 或手動加入該 Header)。
# 普通表單提交 accept: */* referer: https://localhost:8080/user2/toQueryPage.action user-agent: Mozilla/4.0 ... # 無 X-Requested-With # AJAX 請求 (jQuery) x-requested-with: XMLHttpRequest accept: application/json, text/javascript, */*; q=0.01 content-type: application/x-www-form-urlencoded ...
ServletRequestAware 獲取 Requestpublic abstract class BaseAction <ParamVo extends BaseParamVo, ResultVo extends BaseResultVo>
extends ActionSupport implements ServletRequestAware {
private static final String AJAX_RESULT = "ajaxResult";
private static final String XHR_HEADER_VALUE = "XMLHttpRequest";
private static final String HEADER_NAME = "x-requested-with";
private HttpServletRequest request;
protected ParamVo paramVo;
protected ResultVo resultVo;
@Override
public void setServletRequest(HttpServletRequest request) {
this.request = request;
}
@Override
public String execute() {
String resultPage = SUCCESS;
try {
resultVo = doExecute(paramVo); // 子類實作具體業務
} catch (BaseException e) {
resultPage = ERROR;
}
// 關鍵判斷:若為 AJAX 請求,導向 JSON 結果
if (XHR_HEADER_VALUE.equals(request.getHeader(HEADER_NAME))) {
resultPage = AJAX_RESULT;
}
return resultPage;
}
protected abstract ResultVo doExecute(ParamVo paramVo) throws BaseException;
}
<package name="ajax-default" extends="json-default">
<global-results>
<result name="ajaxResult" type="json">
<param name="excludeNullProperties">true</param>
<param name="root">resultVo</param> <!-- 只序列化 resultVo -->
<param name="ignoreHierarchy">false</param>
</result>
</global-results>
</package>
子 Action 繼承 BaseAction 並指定 extends="ajax-default" 即可自動擁有 AJAX/普通雙模式支援。
Struts2 核心功能多由攔截器實現。檢視 struts-default.xml 可發現 profiling 攔截器:
<interceptor name="profiling" class="org.apache.struts2.interceptor.ProfilingActivationInterceptor" />
public class ProfilingActivationInterceptor extends AbstractInterceptor {
private String profilingKey = "profiling";
private boolean devMode;
@Inject(StrutsConstants.STRUTS_DEVMODE)
public void setDevMode(String mode) { this.devMode = "true".equals(mode); }
@Override
public String intercept(ActionInvocation invocation) throws Exception {
if (devMode) {
Object val = invocation.getInvocationContext().getParameters().get(profilingKey);
if (val != null) {
String sval = (val instanceof String) ? (String)val : ((String[])val)[0];
boolean enable = "yes".equalsIgnoreCase(sval) || "true".equalsIgnoreCase(sval);
UtilTimerStack.setActive(enable); // 啟動/關閉計時器
invocation.getInvocationContext().getParameters().remove(profilingKey);
}
}
return invocation.invoke();
}
}
觸發條件:struts.devMode=true 且請求參數含 profiling=true 或 yes。
<package name="ajax-default" extends="json-default">
<interceptors>
<interceptor-stack name="ajaxInterceptorStack">
<interceptor-ref name="defaultStack" />
<interceptor-ref name="profiling" /> <!-- 新增 profiling -->
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="ajaxInterceptorStack" />
<global-results> ... </global-results>
</package>
function searchAllUser() {
jQuery.ajax({
type: "post",
url: "searchAllUser.action",
dataType: 'json',
data: jQuery("#userQueryForm").serialize() + "&profiling=yes", // 關鍵參數
success: function(data) { /* ... */ }
});
}
[Profiling] Action [searchAllUser] took 125ms [Profiling] Method execute took 110ms [Profiling] Result json took 15ms
輸出包含 Action 總耗時、業務方法耗時、Result 渲染耗時,精準定位效能瓶頸。
X-Requested-With Header,前端統一使用 jQuery 或攔截器統一添加。BaseAction,子類專注業務。root 參數限制序列化範圍,避免循環引用與敏感資料洩露。devMode 與 profiling,生產環境關閉 devMode 避免開銷。defaultStack + 業務攔截器(權限、日誌、事務、剖析),避免疊加不必要攔截器。善用 Struts2 內建機制(Header 判斷、結果類型、Profiling 攔截器),能以最小代價實現 AJAX 雙模式支援與效能可視化,是框架深度使用者的必修課。