Struts2 源碼分析——Action代理類的工作


章節簡言

上一章筆者講到關於如何加載配置文件里面的package元素節點信息。相信讀者到這里心里面對struts2在啟動的時候加載相關的信息有了一定的了解和認識。而本章將講到關於struts2啟動成功之后,接受到用戶action請求之后如何處理並找到對應的action類。可以說這章是講述《Struts2 源碼分析——調結者(Dispatcher)之執行action》章節之后的事情。即是核心機制圖片的藍色(Struts core)分部的知識點。通過前面幾章節的內容至少我們知道了struts2啟動成之后,會把相關的信息存放在Container容器和DefaultConfiguration類的實例里面。而Dispatcher類的實例便是這倆個類的中間調節者。(不懂得的讀者請先查看一下前面幾章節來在)

 Action代理類的新建

通過《Struts2 源碼分析——調結者(Dispatcher)之執行action》章節我們知道執行action請求,最后會落到Dispatcher類的serviceAction方法上面。可惜筆者並沒有在這一章里面對他自己詳細的講解。先讓我們看一下代碼吧?知道他在做什么吧。如下

Dispatcher類:

 1 public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)
 2             throws ServletException {
 3 
 4         Map<String, Object> extraContext = createContextMap(request, response, mapping);
 5 
 6         //如果之前就有了值棧,就是新建一個新的值棧,放入extraContext
 7         ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
 8         boolean nullStack = stack == null;
 9         if (nullStack) {
10             ActionContext ctx = ActionContext.getContext();
11             if (ctx != null) {
12                 stack = ctx.getValueStack();
13             }
14         }
15         if (stack != null) {
16             extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
17         }
18 
19         String timerKey = "Handling request from Dispatcher";
20         try {
21             UtilTimerStack.push(timerKey);
22             String namespace = mapping.getNamespace();//獲得request請求里面的命名空間,即是struts.xml是的package節點元素
23             String name = mapping.getName();//獲得request請求里面的action名
24             String method = mapping.getMethod();//要執行action的方法
25 
26             ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name,
27                     method, extraContext, true, false);//獲得action的代理
28 
29             request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
30 
31             // 如果action映射是直接就跳轉到網頁的話,
32             if (mapping.getResult() != null) {
33                 Result result = mapping.getResult();
34                 result.execute(proxy.getInvocation());
35             } else {
36                 proxy.execute();//這里就是執行action
37             }
38 
39             
40             //
41             if (!nullStack) {
42                 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
43             }
44         } catch (ConfigurationException e) {
45             logConfigurationException(request, e);
46             sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);
47         } catch (Exception e) {
48             if (handleException || devMode) {
49                 sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
50             } else {
51                 throw new ServletException(e);
52             }
53         } finally {
54             UtilTimerStack.pop(timerKey);
55         }
56     }

1.根據傳入的參數request, response, mapping來新建一個上下文Map。上下文Map就是一個存了關於RequestMap類,SessionMap類,ApplicationMap類等實例。即是request請求相關的信息,只是把他變成了對應的MAP類而以。

2.從request請求中找到對應的值棧(ValueStack)。如果沒有就新建值棧。然后存放到上下文Map里面,對應的KEY為ActionContext.VALUE_STACK常量的值。即是"com.opensymphony.xwork2.util.ValueStack.ValueStack"。

3.從Mapping參數中提取對應的request請求的命名空間,action名字和方法名。

4.從Container容器中找到ActionProxyFactory類,並根據request請求的命名空間,action名字和方法名,上下文Map來獲得對應的action代理類(ActionProxy)。然后更新request請求中的對應的值棧(ValueStack)。

5.根據Mapping參數來判斷是否為直接輸出結果。還是執行action代理類。

6.最后在判斷之前是否request請求沒有找到對應的值棧(ValueStack)。如果有找到值棧(ValueStack),則更新request請求中的對應的值棧(ValueStack)。

所以我們的目標很明確就是要去看一下action代理類(ActionProxy)。了解他到底做了什么。才能明白如何找到對應的action類,並執行對應的方法。從上面我們也知道action代理類的新建是通過ActionProxyFactory接口實例來進行的。即是DefaultActionProxyFactory類的實例。顯然就是一個簡章的工廠模式。讓我們看一下新建action代理類的代碼吧。

DefaultActionProxyFactory類:

1   public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {
2         
3         ActionInvocation inv = createActionInvocation(extraContext, true);
4         container.inject(inv);
5         return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
6     }

看到了吧。在新建action代理類的時候還要用到ActionInvocation接口的實例。即是DefaultActionInvocation類的實例。前面幾章筆者曾經講過Dispatcher類才是正真執行action類實例的人。這里筆者不得不在提一下。Dispatcher類是重要的調結者,DefaultActionInvocation類是執行action類實例的行動者。而action代理類(ActionProxy類)則是他們之間的中間人。相當於Dispatcher類通過action代理類(ActionProxy類)命令DefaultActionInvocation類去執行action類實例。

Action代理類的准備工作

action代理類(ActionProxy類)在命令DefaultActionInvocation類去執行action類實例之前,還是有做了一些准備工作。好吧。筆者還是希望通過代碼來說話。看一下代碼吧。

DefaultActionProxyFactory類:

1 public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {
2 
3         DefaultActionProxy proxy = new DefaultActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
4         container.inject(proxy);
5         proxy.prepare();
6         return proxy;
7     }

DefaultActionProxy類:

 1 protected void prepare() {
 2         String profileKey = "create DefaultActionProxy: ";
 3         try {
 4             UtilTimerStack.push(profileKey);
 5             config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName);//根據空間命名和action名來找到對應的配置信息
 6 
 7             if (config == null && unknownHandlerManager.hasUnknownHandlers()) {
 8                 config = unknownHandlerManager.handleUnknownAction(namespace, actionName);
 9             }
10             if (config == null) {
11                 throw new ConfigurationException(getErrorMessage());
12             }
13 
14             resolveMethod();//找到對應的方法名。
15 
16             if (config.isAllowedMethod(method)) {
17                 invocation.init(this);
18             } else {
19                 throw new ConfigurationException(prepareNotAllowedErrorMessage());
20             }
21         } finally {
22             UtilTimerStack.pop(profileKey);
23         }
24     }

從上面的代碼,我們可以看出在執行action類之前,大概做了倆件准備工作:

1.獲得ActionConfig類實例。並通過ActionConfig類實例找到對應的方法名。ActionConfig類就是存放配置文件里面的action元素節點的信息。

2.實初始化DefaultActionInvocation類的實例。即是根據ActionProxy類實例找到對應的action類實例(用戶自己定義的類)。

代碼中倆個方法是筆者希望讀者明白的。一個是DefaultActionProxy類的resolveMethod方法。一個是DefaultActionInvocation類的init方法。為什么要講這倆個方法。上面的倆件事情主要的功能都是在這倆個方法里面。讓我們看一代碼吧?

 DefaultActionProxy類:

 1  private void resolveMethod() {
 2         // 從配置中獲得方法名。如果還是空的話,就用默認的值。即是"execute"方法。
 3         if (StringUtils.isEmpty(this.method)) {
 4             this.method = config.getMethodName();
 5             if (StringUtils.isEmpty(this.method)) {
 6                 this.method = ActionConfig.DEFAULT_METHOD;
 7             }
 8             methodSpecified = false;
 9     }
10     }

DefaultActionInvocation類:

 1 public void init(ActionProxy proxy) {
 2         this.proxy = proxy;
 3         Map<String, Object> contextMap = createContextMap();
 4 
 5         // Setting this so that other classes, like object factories, can use the ActionProxy and other
 6         // contextual information to operate
 7         ActionContext actionContext = ActionContext.getContext();
 8 
 9         if (actionContext != null) {
10             actionContext.setActionInvocation(this);
11         }
12 
13         createAction(contextMap);//找到對應的action類實例
14 
15         if (pushAction) {
16             stack.push(action);
17             contextMap.put("action", action);
18         }
19 
20         invocationContext = new ActionContext(contextMap);
21         invocationContext.setName(proxy.getActionName());
22 
23         createInterceptors(proxy);
24     }

看了代碼就能清楚的知道一件事情。如果我們在struts.xml配置文件里面action元素節點里面沒有指定方法的時候,就用會默認的方法。即是execute方法。而關於init方法就能明確明白為了找到action類並實例他。init方法里面調用了倆個非重要的方法。一個是用於新建action類實例的方法createAction。一個是用於獲得相關攔截器的方法createInterceptors。看一下代碼吧。

DefaultActionInvocation類:

 1   protected void createAction(Map<String, Object> contextMap) {
 2         // load action
 3         String timerKey = "actionCreate: " + proxy.getActionName();
 4         try {
 5             UtilTimerStack.push(timerKey);
 6             action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
 7         } catch (InstantiationException e) {
 8             throw new XWorkException("Unable to instantiate Action!", e, proxy.getConfig());
 9         } catch (IllegalAccessException e) {
10             throw new XWorkException("Illegal access to constructor, is it public?", e, proxy.getConfig());
11         } catch (Exception e) {
12             String gripe;
13 
14             if (proxy == null) {
15                 gripe = "Whoa!  No ActionProxy instance found in current ActionInvocation.  This is bad ... very bad";
16             } else if (proxy.getConfig() == null) {
17                 gripe = "Sheesh.  Where'd that ActionProxy get to?  I can't find it in the current ActionInvocation!?";
18             } else if (proxy.getConfig().getClassName() == null) {
19                 gripe = "No Action defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
20             } else {
21                 gripe = "Unable to instantiate Action, " + proxy.getConfig().getClassName() + ",  defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
22             }
23 
24             gripe += (((" -- " + e.getMessage()) != null) ? e.getMessage() : " [no message in exception]");
25             throw new XWorkException(gripe, e, proxy.getConfig());
26         } finally {
27             UtilTimerStack.pop(timerKey);
28         }
29 
30         if (actionEventListener != null) {
31             action = actionEventListener.prepare(action, stack);
32         }
33     }

DefaultActionInvocation類:

1  protected void createInterceptors(ActionProxy proxy) {
2         // Get a new List so we don't get problems with the iterator if someone changes the original list
3         List<InterceptorMapping> interceptorList = new ArrayList<>(proxy.getConfig().getInterceptors());
4         interceptors = interceptorList.iterator();
5     }

相信讀者一定能看的懂代碼吧。倆個方法中在筆者看來最重要的體現便是ObjectFactory類。ObjectFactory類是用於新一個實例的。上面的方法里面就是用ObjectFactory類來創建一個action類的實例。

好了。到了這里面action代理類(ActionProxy類)的准備工作算是做完了。讓筆者理一下。准備工作完成之后。筆者至少知道action類實例有了。要執行的方法名也有了。要執行的攔截器也有了。有了這些信息難道strtus2會不知道去執行對應的工作嗎?

Action代理類的主要工作

action代理類(ActionProxy類)的准備工作完成之后,就開始執行了。最頂部的代碼中就很明確的看的出來(serviceAction方法)。先是根據參數mapping來判斷是否為直接回返。如果不是才去執行action代理類(ActionProxy類)的execute方法。這便是action代理類(ActionProxy類)的主要工作。即是執行action請求。那么讓我們看一下action代理類(ActionProxy類)的execute方法源碼吧。

 1   public String execute() throws Exception {
 2         ActionContext nestedContext = ActionContext.getContext();
 3         ActionContext.setContext(invocation.getInvocationContext());
 4 
 5         String retCode = null;
 6 
 7         String profileKey = "execute: ";
 8         try {
 9             UtilTimerStack.push(profileKey);
10 
11             retCode = invocation.invoke();
12         } finally {
13             if (cleanupContext) {
14                 ActionContext.setContext(nestedContext);
15             }
16             UtilTimerStack.pop(profileKey);
17         }
18 
19         return retCode;
20     }

很好。從紅色的代碼部分我們就知道就是去執行DefaultActionInvocation類實例的invoke方法。DefaultActionInvocation類和action代理類(ActionProxy類)的關系看起相當的復雜。可以說是我中有你,你中有我。DefaultActionProxy類新建的時候需要DefaultActionInvocation類的實例。而DefaultActionInvocation類的實例初始化的時候,action代理類(ActionProxy類)的實例會傳DefaultActionInvocation類里面並存放起來。即是在init方法的時候。值得注意的是這個時候的Action上下文(ActionContext類)有發生一件細微的變化。不是以前的了。而是從DefaultActionInvocation類的實例中得來的。cleanupContext參數表示要不要執行完成之后就清除掉當前的。把原來的放在去。最后回返結果。

本章總結

本章主要是講到關於action代理類(ActionProxy類)的工作。知道了DefaultActionInvocation類是用去執行action類的行動者。而Dispatcher類是調結者。action代理類(ActionProxy類)是DefaultActionInvocation類和Dispatcher類的中間人。即是Dispatcher類通過action代理類(ActionProxy類)命令DefaultActionInvocation類去執行action類實例。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM