在 MVC 架構中,ActionServlet 扮演著前端控制器 的角色:攔截所有符合映射規則的請求,委派具體業務處理給 Action,最後由 ActionRouter 完成視圖導航。本文深度剖析其核心代碼、URL 解析邏輯及類加載器交互細節。
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>ActionServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
JSP 表單提交示例:
<form name="FORM1" method="POST" action="LoginAction.do">
<!-- 表單字段 -->
</form>
public class ActionServlet extends HttpServlet {
private ActionFactory factory = new ActionFactory();
public void init(ServletConfig config) throws ServletException {
// 生命週期:容器啟動時僅執行一次,適合初始化昂貴資源(連接池、配置緩存等)
super.init(config);
}
public void service(HttpServletRequest req, HttpServletResponse res)
throws IOException, ServletException {
try {
// 1. 從請求路徑解析 Action 類全限定名
String className = getClassname(req); // 如 "actions.LoginAction"
// 2. 從工廠獲取 Action 實例(單例緩存)
Action action = factory.getAction(className, getClass().getClassLoader());
// 3. 執行業務邏輯,獲取路由結果
ActionRouter router = action.perform(this, req, res);
// 4. 執行視圖跳轉(轉發/重定向)
router.route(this, req, res);
} catch (Exception e) {
throw new ServletException(e);
}
}
/**
* 從請求路徑 /actions/LoginAction.do 提取類名 actions.LoginAction
*/
public String getClassname(HttpServletRequest req) {
String path = req.getServletPath(); // 返回 /actions/LoginAction.do
int begin = path.lastIndexOf("/") + 1; // 最後一個 '/' 後
int end = path.lastIndexOf("."); // 最後一個 '.' 前
if (begin > 0 && end > begin) {
return path.substring(begin, end);
}
return path; // 兜底
}
}
factory 為實例變量,但 Hashtable 內部同步,Action 實例需為無狀態或線程安全。getClass().getClassLoader() 返回什麼?// 運行時輸出示例
getClass() -> class ActionServlet
getClassLoader() -> sun.misc.Launcher$AppClassLoader@92e78c (或 WebAppClassLoader)
此 ClassLoader 負責加載 actions.LoginAction 等業務類。在 Web 容器中,通常是 WebAppClassLoader,實現了 Web 應用級的類隔離。
Bootstrap ClassLoader (C++ 實現)
↑ 父加載器
Extension ClassLoader (sun.misc.Launcher$ExtClassLoader)
↑ 父加載器
AppClassLoader / WebAppClassLoader (sun.misc.Launcher$AppClassLoader)
↑ 加載應用類 (WEB-INF/classes, WEB-INF/lib/*.jar)
雙親委派模型:加載類時自下而上委派,找不到再自行加載。優勢:
核心動機:解耦與擴展性
Action 接口,具體實現由運行時動態確定。| 特性 | ActionServlet (早期 MVC) | Spring MVC DispatcherServlet | Struts 2 / WebWork |
|---|---|---|---|
| URL 映射 | 硬編碼 *.do + 字符串解析 | HandlerMapping (注解/配置) | ActionMapper (命名空間/約定) |
| Controller 實例化 | 工廠 + 反射單例 | IoC 容器管理 Bean 生命週期 | 每請求一個 Action 實例 (原型) |
| 類加載 | 顯式 ClassLoader | 容器默認 + Spring 加載器 | 容器默認 |
| 線程模型 | 單例多線程 (需無狀態) | 單例 Bean (需無狀態) | 每請求一實例 (天然線程安全) |
ActionServlet 以極簡代碼展示了前端控制器模式的精髓:統一入口、動態分發、關注點分離。其 URL 解析邏輯、工廠緩存策略、類加載器交互,均是後續成熟 MVC 框架的原型。透過原理掌握本質,面對 Spring MVC 的 HandlerMapping、HandlerAdapter、ViewResolver 等組件,將不再感到陌生。