專案從 Tomcat 遷移至 Apusic 應用服務器時拋出 ClassCastException,定位代碼:
HashMap params = (HashMap) request.getParameterMap();
| 維度 | Servlet API 規範 (JCP) | Tomcat 實現 | Apusic / 其他標準容器 |
|---|---|---|---|
| 方法簽名 | public Map getParameterMap() | 同上 | 同上 |
| 返回類型契約 | java.util.Map (不可變、Key=String、Value=String[]) | org.apache.catalina.util.ParameterMap 繼承 HashMap | 通常返回 Collections.unmodifiableMap(...) 或自有 Map 實現,不保證是 HashMap |
| 可變性 | 不可變 | ParameterMap 可鎖定,預設可變 | 不可變 |
Tomcat 為了效能與內部便利,返回具體實現類 ParameterMap extends HashMap,開發者誤以為契約就是 HashMap,於是大膽強轉。一旦換容器,契約只保證 Map,強轉即失敗。
// ✅ 推薦:僅依賴 Map 介面
Map params = request.getParameterMap();
// 需要可變副本時
Map mutableParams = new HashMap<>(request.getParameterMap());
// 需要單值獲取 (常見場景)
String username = request.getParameter("username"); // 直接取首個值
String[] hobbies = request.getParameterValues("hobby"); // 取陣列
(HashMap)、(ArrayList)、(LinkedList) 等具體實現類轉型org.apache.catalina.*、org.apache.tomcat.*、org.apache.jasper.*application、session 具體實現類若歷史代碼大量強轉 HashMap,可引入工具方法統一轉換:
public final class ServletUtils {
public static Map getParameterMapAsHashMap(HttpServletRequest request) {
Map src = request.getParameterMap();
return (src instanceof HashMap) ? (HashMap) src : new HashMap<>(src);
}
}
// 使用
HashMap params = ServletUtils.getParameterMapAsHashMap(request);
將所有 (HashMap) request.getParameterMap() 批量替換為工具方法調用,完成遷移後再逐步重構為面向介面寫法。
RequestWrapper、ResponseWrapper,隔離容器差異「在 Tomcat 上跑通 ≠ 符合規範」。Servlet 容器可移植性的核心是嚴格面向介面編程。養成查閱規格文檔、拒絕具體實現類強轉的習慣,能為專案節省大量遷移成本。