1. 程式人生 > >Struts執行流程和攔截器之美

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)。

上述過程中所有的物件(Action、Interceptors、Results等)都由 xwork容器中的ObjectFactory建立


攔截器: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

 print?

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

 print?

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

 print?

1.  dispatcher.serviceAction(request, response, servletContext, mapping);  


    從上面可以看出,FilterDispatcher類中是呼叫的serviceAction方法來尋找的去呼叫哪一個action。serviceAction()方法作用:載入Action 類,呼叫Action 類的方法,轉向到響應結果。響應結果指<result/> 標籤所代表的物件。

步驟四、五、六:如果ActionMapper 確定需要呼叫某個ActionFilterDispatcher 將控制權交給ActionProxy

我們來看一下具體的serviceAction原始碼:

[java] view plain copy

 print?

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, truefalse);  

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

 print?

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的整個流程就基本差不多了,從頭到尾看下來,說實話,感觸很多,很多不明白的地方,這算是近了自己最大的努力去看這些原始碼,感覺從裡面收穫了很多,裡面很多的機制和知識點值得我們去學習。