Struts執行流程和攔截器之美
Struts2的請求的執行步驟:
-----------------------------------------------------------
①.客戶端傳送請求;
②.該請求經過一系列的過濾器(Filter):其中可選過濾器ActionContextCleanUp,幫助Struts2和其他框架整合。例如:SiteMeshPlugin。
③.接著FilterDispatcher前段過濾器被呼叫,FilterDispatcher詢問ActionMapper,來決定該請求是否需要呼叫某個Action。
④.若ActionMapper決定需要呼叫某個Action,FilterDispatcher把請求的處理交給ActionProxy
⑤.ActionProxy通過ConfigurationManager詢問框架的配置檔案,找到需要呼叫的Action類。
⑥.ActionProxy建立一個ActionInvocation的例項。
⑦.ActionInvocation例項呼叫Action的前後,涉及到相關攔截器(Intercepter)的呼叫。
⑧.一旦Action執行完畢,ActionInvocation負責根據struts.xml中的配置找到對應的返回結果。返回結果是一個JSP或其他頁面(也可以是其他的Action鏈)。JSP頁面展現可使用Struts2框架中的標籤(該過程會涉及ActionMapper)。
攔截器:Interceptor
------------------------------
攔截器:Struts2攔截器是在訪問某個Action或Action的某個方法之前或之後實施攔截,並且Struts2攔截器是可插拔的,攔截器是AOP的一種實現.
AOP:面向切面程式設計.其實現原理:動態代理模式--->留給Spring
WebWork中文文件解釋:攔截器是動態攔截Action呼叫的物件。它提供了一種機制可以使開發者可以定義在一個Action執行的前後執行的程式碼,也可以在一個action執行前阻止其執行。同時也提供了一種可以提取Action中可重用的程式碼的方式。
攔截器棧(InterceptorStack):Struts2攔截器棧就是將攔截器按一定的順序連線成一條鏈。在訪問被攔截的方法或欄位時,Struts2攔截器鏈中的攔截器就會按其之前定義的順序被呼叫。
-------------------------------------------------------------------------
攔截器的"美":
---------------------------------------------------
DRY原則:Dont'tRepeat Yourself.
攔截器在設計和程式結構上的優點:
攔截器能把很多功能從Action中獨立出來,分散到不同的攔截器裡面,減少了Action的程式碼。如此,攔截器和Action本身的功能都更單一了。當通用的功能程式碼被封裝在攔截器裡面(程式碼模組化),就可以對不同的Action,根據功能需要,來配置相應功能的攔截器了。提高了攔截器所實現的功能的重用性,也變相實現了裝配式和可插拔式的體系結構,使得整個系統結構變得更靈活。
1.簡化Action的實現
2.功能更單一
3.通用程式碼模組化
4.提高重用性
下面我們就來具體分析一下3-6四個步驟:
步驟三:FilterDispatcher 或者說 StrutsPrepareAndExecuteFilter 查詢ActionMapper,以確定這個請求是否需要呼叫某個Action。
1)
[java] view plain copy
1. ActionMapping mapping;
2. try {
3.
4. mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());
5.
6. } catch (Exception ex) {
7.
8. log.error("error getting ActionMapping", ex);
9.
10. dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
11.
12. return;
13. }
2)呼叫actionmapper去尋找對應的ActionMapping,因為actionmapper是一個介面,所有我們去他對應的實現類(DefaultActionMapper)裡面去找getMapping方法,下面我們來看一下實現類裡面的getMapping方法原始碼:
[java] view plain copy
1. public ActionMapping getMapping(HttpServletRequest request,
2.
3. ConfigurationManager configManager) {
4.
5. ActionMapping mapping = new ActionMapping();
6.
7. String uri = getUri(request);
8. int indexOfSemicolon = uri.indexOf(";");
9.
10. uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;
11.
12. uri = dropExtension(uri, mapping);
13.
14. if (uri == null) {
15.
16. return null;
17.
18. }
19.
20. parseNameAndNamespace(uri, mapping, configManager);
21.
22. handleSpecialParameters(request, mapping);
23.
24. if (mapping.getName() == null) {
25.
26. return null;
27.
28. }
29. parseActionName(mapping);
30.
31. return mapping;
32. }
ActionMapping 代表struts.xml 檔案中的一個Action 配置,被傳入到serviceAction 中。注意ActionMapping 不代表Action 集合,只代表某個對應的Action。如果是一個Action 請求,( 請求路徑在struts.xml 有對應的Action 配置,即actionmapping不為空),則呼叫dispatcher.serviceAction() 處理。找到對應的ActionMapping,下一步就去找具體的執行哪一個action,從FilterDispatcher原始碼中我們可以找到下一步流程:
[java] view plain copy
1. dispatcher.serviceAction(request, response, servletContext, mapping);
從上面可以看出,FilterDispatcher類中是呼叫的serviceAction方法來尋找的去呼叫哪一個action。serviceAction()方法作用:載入Action 類,呼叫Action 類的方法,轉向到響應結果。響應結果指<result/> 標籤所代表的物件。
步驟四、五、六:如果ActionMapper 確定需要呼叫某個Action,FilterDispatcher 將控制權交給ActionProxy。
我們來看一下具體的serviceAction原始碼:
[java] view plain copy
1. public void serviceAction(HttpServletRequest request, HttpServletResponse response,
2.
3. ServletContext context, ActionMapping mapping) throws ServletException {
4.
5. Map<String, Object> extraContext = createContextMap
6.
7. (request, response, mapping, context);
8.
9. //1 以下程式碼目的為獲取ValueStack,代理在呼叫的時候使用的是本值棧的副本
10.
11. ValueStack stack = (ValueStack) request.getAttribute
12.
13. (ServletActionContext.STRUTS_VALUESTACK_KEY);
14.
15. boolean nullStack = stack == null;
16.
17. if (nullStack) {
18.
19. ActionContext ctx = ActionContext.getContext();
20.
21. if (ctx != null) {
22.
23. stack = ctx.getValueStack();
24.
25. }
26.
27. }
28.
29. //2 建立ValueStack 的副本
30.
31. if (stack != null) {
32.
33. extraContext.put(ActionContext.VALUE_STACK,
34.
35. valueStackFactory.createValueStack(stack));
36.
37. }
38.
39. String timerKey = "Handling request from Dispatcher";
40.
41. try {
42.
43. UtilTimerStack.push(timerKey);
44.
45. //3 這個是獲取配置檔案中<action/> 配置的字串,action 物件已經在核心控制器中建立
46.
47. String namespace = mapping.getNamespace();
48.
49. String name = mapping.getName();
50.
51. String method = mapping.getMethod();
52.
53. // xwork 的配置資訊
54.
55. Configuration config = configurationManager.getConfiguration();
56.
57. //4 動態建立ActionProxy
58.
59. ActionProxy proxy =
60.
61. config.getContainer().getInstance(ActionProxyFactory.class).
62.
63. createActionProxy(namespace, name, method, extraContext, true, false);
64.
65. request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY,
66.
67. proxy.getInvocation().getStack());
68.
69. //5 呼叫代理
70.
71. if (mapping.getResult() != null) {
72.
73. Result result = mapping.getResult();
74.
75. result.execute(proxy.getInvocation());
76.
77. } else {
78.
79. proxy.execute();
80.
81. }
82.
83. //6 處理結束後,恢復值棧的代理呼叫前狀態
84.
85. if (!nullStack) {
86.
87. request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
88.
89. }
90.
91. } catch (ConfigurationException e) {
92.
93. //7 如果action 或者result 沒有找到,呼叫sendError 報404 錯誤
94.
95. }
關於valuestack說明一下:
1.valueStack 的建立是在doFilter 的開始部分,在Action 處理之前。即使訪問靜態資源ValueStack 依然會建立,儲存在request 作用域。
2. ValueStack 在邏輯上包含2 個部分:object stack 和context map,object stack 包含Action 與Action 相關的物件。
context map 包含各種對映關係。request,session,application,attr,parameters 都儲存在context map 裡。
parameters: 請求引數
atrr: 依次搜尋page, request, session, 最後application 作用域。
幾點說明:
1. Valuestack 物件儲存在request 裡,對應的key 是ServletActionContext.STRUTS_VALUESTACK_KEY。呼叫代理之前首先建立Valuestack 副本,呼叫代理時使用副本,呼叫後使用原例項恢復。本處的值棧指object stack。
2. Dispatcher 例項,建立一個Action 代理物件。並把處理委託給代理物件的execute 方法。
最後我們在一起看一下ActionInvocation實現類中invoke方法執行的流程:invoke原始碼:
[java] view plain copy
1. public String invoke() throws Exception {
2.
3. String profileKey = "invoke: ";
4.
5. try {
6.
7. UtilTimerStack.push(profileKey);
8.
9. if (executed) {
10.
11. throw new IllegalStateException("Action has already executed");
12.
13. }
14.
15. if (interceptors.hasNext()) {
16.
17. final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
18.
19. String interceptorMsg = "interceptor: " + interceptor.getName();
20.
21. UtilTimerStack.push(interceptorMsg);
22.
23. try {
24.
25. resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
26.
27. }
28.
29. finally {
30.
31. UtilTimerStack.pop(interceptorMsg);
32.
33. }
34.
35. } else {
36.
37. resultCode = invokeActionOnly();
38.
39. }
40.
41.
42. if (!executed) {
43.
44. if (preResultListeners != null) {
45.
46. for (Object preResultListener : preResultListeners) {
47.
48. PreResultListener listener = (PreResultListener) preResultListener;
49.
50.
51. String _profileKey = "preResultListener: ";
52.
53. try {
54.
55. UtilTimerStack.push(_profileKey);
56.
57. listener.beforeResult(this, resultCode);
58.
59. }
60.
61. finally {
62.
63. UtilTimerStack.pop(_profileKey);
64.
65. }
66.
67. }
68.
69. }
70.
71.
72. if (proxy.getExecuteResult()) {
73.
74. executeResult();
75.
76. }
77.
78.
79. executed = true;
80.
81. }
82. return resultCode;
83.
84. }
85.
86. finally {
87.
88. UtilTimerStack.pop(profileKey);
89.
90. }
91. }
這裡算是執行action中方法的最後一步了吧,至此,action的整個流程就基本差不多了,從頭到尾看下來,說實話,感觸很多,很多不明白的地方,這算是近了自己最大的努力去看這些原始碼,感覺從裡面收穫了很多,裡面很多的機制和知識點值得我們去學習。