Struts2請求處理流程及源碼分析
根據Web.xml配置,請求首先經過ActionContextCleanUp過濾器,其為可選過濾器,這個過濾器對于Struts2和其他架構的內建很有幫助(SiteMesh Plugin),主要清理目前線程的ActionContext和Dispatcher;
1.1 Struts2請求處理
1. 一個請求在Struts2架構中的處理步驟:
a) 用戶端初始化一個指向Servlet容器的請求;
b) 根據Web.xml配置,請求首先經過ActionContextCleanUp過濾器,其為可選過濾器,這個過濾器對于Struts2和其他架構的內建很有幫助(SiteMesh Plugin),主要清理目前線程的ActionContext和Dispatcher;
c) 請求經過插件過濾器,如:SiteMesh、etc等過濾器;
d) 請求經過核心過濾器FilterDispatcher,執行doFilter方法,在該方法中,詢問ActionMapper來決定這個請求是否需要調用某個Action;
e) 如果ActionMapper決定需要調用某個Action,則ActionMapper會傳回一個ActionMapping執行個體(存儲Action的配置資訊),并建立ActionProxy(Action代理)對象,将請求交給代理對象繼續處理;
f) ActionProxy對象是根據ActionMapping和Configuration Manager詢問架構的配置檔案,找到需要調用的Action類;
g) ActionProxy對象建立時,會同時建立一個ActionInvocation的執行個體;
h) ActionInvocation執行個體使用命名模式來調用,在調用Action的過程前後,涉及到相關攔截器(Intercepter)的調用;
i) 一旦Action執行完畢,ActionInvocation執行個體負責根據struts.xml中的配置建立并傳回Result。Result通常是一個需要被表示的JSP或者FreeMarker的模版,也可能是另外的一個Action鍊;
j) 如果要在傳回Result之前做些什麼,可以實作PreResultListener接口,PreResultListener可以在Interceptor中實作,也可以在Action中實作;
k) 根據Result對象資訊,生成使用者響應資訊response,在生成響應過程中可以使用Struts2 架構中繼承的标簽,在此過程中仍會再次涉及到ActionMapper;
2. Struts2請求處理示意圖:
1.2 Struts2請求處理源碼分析
當使用者向Struts2發送請求時,FilterDispatcher的doFilter()方法自動調用,doFilter()方法處理請求過程,如下:
1. 建立值棧對象stack;
2. 建立Action上下文對象;
3. 對請求進行重新封裝,此次封裝根據請求内容的類型不同,傳回不同的對象:
如果為multipart/form-data類型,則傳回MultiPartRequestWrapper類型的對象,該對象服務于檔案上傳,否則傳回StrutsRequestWrapper類型的對象,MultiPartRequestWrapper是StrutsRequestWrapper的子類,而這兩個類都是HttpServletRequest接口的實作。
4. 通過actionMapper.getMapping()獲得ActionMapping對象,Action的配置資訊存儲在ActionMapping對象中(Action的配置資訊:Action的name、namespace和要調用的方法method)。相關代碼如下圖所示:
以上代碼,活動圖如下:
5. 如果getMapping()方法傳回ActionMapping對象為null,則FilterDispatcher認為使用者請求不是Action,此時FilterDispatcher會首先分析:
如果請求以/struts開頭,會自動查找在web.xml檔案中配置的packages初始化參數,FilterDispatcher會将packages參數值包下的檔案當作靜态資源處理,即直接在頁面上顯示檔案内容。
如果使用者請求的資源不是以/struts開頭—可能是.jsp檔案,也可能是.html檔案,則通過過濾器鍊繼續往下傳送,直到到達請求的資源為止。
6. 如果getMapping()方法傳回有效的ActionMapping對象,則被認為正在請求某個Action,将調用Dispatcher.serviceAction(request, response, servletContext, mapping)方法。
以上六步,相關代碼如下圖所示:
7. 請求進入dispatcher.serviceAction(request,response,servletContext,mapping)方法中:
a) 将相關對象資訊封裝為Map(如:HttpServletRequest、Http parameters、HttpServletResponse、HttpSession、ServletContext、ActionMapping等對象資訊),并存入到執行上下文Map中,傳回執行上下文Map對象extraMap;
b) 擷取mapping對象中存儲的action命名空間、name屬性、method屬性等資訊;
c) 加載并解析Struts2配置檔案,如果沒有人為配置,預設按順序加載struts-default.xml、struts-plugin.xml、struts.xml,将action配置、result配置、interceptor配置,解析并存入至config對象中,傳回檔案配置對象config;
d) 根據執行上下文Map、action命名空間、name屬性、method屬性等建立使用者Action的代理對象;
e) 執行Action代理對象proxy.execute()方法,并轉向結果;
以上步驟相關代碼,如圖所示:
8. 執行Action代理對象proxy.execute()方法,該方法的執行,其實就是調用了invocation.invoke()方法,如下圖所示:
9. 執行invocation.invoke()方法,實作了截攔器的遞歸調用和執行Action的execute()方法,DefaultActionInvocation.invoke()方法中代碼,如下圖所示:
在以上代碼中,并未看出攔截器的遞歸調用,其實是否遞歸調用,是由程式員來控制的,遞歸調用實作很簡單:
a) 首先看下Interceptor接口定義:
b) 所有的截攔器必須實作intercept方法,而該方法的參數恰恰又是ActionInvocation,是以如果在intercept方法中調用invocation.invoke(),則會繼續從Action的Intercepor清單中找到下一個截攔器執行,依此遞歸調用Intercepor;
Struts2中的日志攔截器LoggingInterceptor,如下圖所示:
c) 攔截器遞歸調用活動圖,如下所示:
10. 在invocation.invoke()方法中,執行攔截器、action并獲得resultCode完畢後,則會繼續執行PreResultListener集合,并生成Result對象,實作PreResultListener接口,可在傳回Result之前,做些自定義處理,如圖所示
在傳回Result之前,通過PreResultListener實作自定義處理,常用的有兩種方式:一種在Interceptor中實作,一種在Action實作,如圖所示:
以上兩種方式,大家可以發現都是通過匿名内部類的方式實作,其實還有一種方式就是通過在攔截器中實作PreResultListener接口,并實作方法beforeResult方法,即可。如下圖所示:
11. 最後,通過生成Result完成使用者響應;
以上1-11步,為Struts2處理請求的完整流程分析,其相關代碼調用流程,如下圖所示:
原文連結:http://my.oschina.net/xianggao/blog/75514