1. 程式人生 > >struts2流程介紹

struts2流程介紹

1、struts2流程介紹

首先要知道struts2是在webwork的技術基礎上開發的,採用攔截器的機制來處理使用者請求的全新MVC框架。而webwork是建立在xwork的command模式框架之上的基於web的MVC框架。所以總而言之,無論是struts2還是webwork底層都是xwork

從其官方網站的介紹來看,XWork不僅提供了一系列基礎構件,其中包括:一個IoC的容器、強大的表示式語言(OGNL)支援、資料型別轉化、資料校驗框架、可插拔的功能模組(外掛模式)及其配置,並且在這一系列的基礎構件之上,實現了一套基於Command設計模式的“事件請求執行框架”。 
那麼,XWork作為Struts2所依賴的底層核心,使得Struts2只需要關注與Web容器打交道的部分,而把其餘的工作交給XWork即可。當Struts2收到一個Http請求時,Struts2只需要接收請求引數,交給XWork完成執行序列,當XWork執行完畢後,將結果交還Struts2返回相應的檢視。

我這裡的struts2原始碼是從官網下載的一個最新的struts-2.3.15.1-src.zip,將其解壓即可。裡面的目錄頁檔案非常的多,我們只需要定位到struts-2.3.15.1\src\core\src\main\java\org\apache\struts2檢視原始檔。目錄結構如下圖:

Struts2框架的正常執行,除了佔核心地位的xwork的支援以外,Struts2本身也提供了許多類,這些類被分門別類組織到不同的包中。從原始碼中發現,基本上每一個Struts2類都訪問了WebWork提供的功能,從而也可以看出Struts2與WebWork千絲萬縷的聯絡。但無論如何,Struts2的核心功能比如將請求委託給哪個Action處理都是由xwork完成的,Struts2只是在WebWork的基礎上做了適當的簡化、加強和封裝,並少量保留Struts1.x中的習慣。 
以下是包說明:

根目錄下的5個檔案說明: 
這裡寫圖片描述

struts2 架構圖如下圖所示: 

依照上圖,我們可以看出一個請求在struts的處理大概有如下步驟:

  1. 客戶端初始化一個指向Servlet容器(例如Tomcat)的請求;
  2. 這個請求經過一系列的過濾器(Filter)(這些過濾器中有一個叫做ActionContextCleanUp的可選過濾器,這個過濾器對於Struts2和其他框架的整合很有幫助,例如:SiteMesh Plugin);
  3. 接著StrutsPrepareAndExecuteFilter被呼叫,執行doFilter()方法,詢問ActionMapper來決定這個請求是否需要呼叫某個Action;
  4. 如果ActionMapper決定需要呼叫某個Action,StrutsPrepareAndExecuteFilter把請求的處理交給ActionProxy;
  5. ActionProxy通過Configuration Manager詢問框架的配置檔案,找到需要呼叫的Action類;
  6. ActionProxy建立一個ActionInvocation的例項。
  7. ActionInvocation例項使用命名模式來呼叫,在呼叫Action的過程前後,涉及到相關攔截器(Intercepter)的呼叫。
  8. 一旦Action執行完畢,ActionInvocation負責根據struts.xml中的配置找到對應的返回結果。返回結果通常是(但不總是,也可能是另外的一個Action鏈)一個需要被表示的JSP或者FreeMarker的模版。在表示的過程中可以使用Struts2 框架中繼承的標籤。在這個過程中需要涉及到ActionMapper。

2、struts2原始碼分析

首先我們使用struts2框架都會在web.xml中註冊和對映struts2,配置內容如下:

<span style="color:#000000"><code> <span style="color:#006666"><<span style="color:#4f4f4f">filter</span>></span> 
     <span style="color:#006666"><<span style="color:#4f4f4f">filter-name</span>></span>struts2<span style="color:#006666"></<span style="color:#4f4f4f">filter-name</span>></span>  
     <span style="color:#006666"><<span style="color:#4f4f4f">filterclass</span>></span>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
     <span style="color:#006666"></<span style="color:#4f4f4f">filter-class</span>></span> 
 <span style="color:#006666"></<span style="color:#4f4f4f">filter</span>></span>  
 <span style="color:#006666"><<span style="color:#4f4f4f">filter-mapping</span>></span> 
     <span style="color:#006666"><<span style="color:#4f4f4f">filter-name</span>></span>struts2<span style="color:#006666"></<span style="color:#4f4f4f">filter-name</span>></span>  
     <span style="color:#006666"><<span style="color:#4f4f4f">url-pattern</span>></span>/*<span style="color:#006666"></<span style="color:#4f4f4f">url-pattern</span>></span> 
 <span style="color:#006666"></<span style="color:#4f4f4f">filter-mapping</span>></span></code></span>

注:在早期的struts2中,都是使用FilterDispathcer,從Struts 2.1.3開始,它已不推薦使用。升級到StrutsPrepareAndExecuteFilter。

StrutsPrepareAndExecuteFilter中的方法: 
這裡寫圖片描述

web容器一啟動,就會初始化核心過濾器StrutsPrepareAndExecuteFilter,並執行初始化init()方法,初始化方法如下:

<span style="color:#000000"><code> <span style="color:#000088">public</span> <span style="color:#000088">void</span> <span style="color:#009900">init</span>(FilterConfig filterConfig) <span style="color:#000088">throws</span> ServletException {
          InitOperations init = <span style="color:#000088">new</span> InitOperations();
          Dispatcher dispatcher = <span style="color:#000088">null</span>;
          <span style="color:#000088">try</span> {
              <span style="color:#880000">//封裝filterConfig,其中有個主要方法getInitParameterNames將引數名字以String格式儲存在List中</span>
              FilterHostConfig config = <span style="color:#000088">new</span> FilterHostConfig(filterConfig);
              <span style="color:#880000">//初始化struts內部日誌</span>
              init.initLogging(config);
              <span style="color:#880000">//建立dispatcher ,並初始化</span>
              dispatcher = init.initDispatcher(config);
              init.initStaticContentLoader(config, dispatcher);
             <span style="color:#880000">//初始化類屬性:prepare 、execute</span>
              prepare = <span style="color:#000088">new</span> PrepareOperations(filterConfig.getServletContext(), dispatcher);
              execute = <span style="color:#000088">new</span> ExecuteOperations(filterConfig.getServletContext(), dispatcher);
              <span style="color:#000088">this</span>.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
             <span style="color:#880000">//回撥空的postInit方法</span>
              postInit(dispatcher, filterConfig);
         } <span style="color:#000088">finally</span> {
              <span style="color:#000088">if</span> (dispatcher != <span style="color:#000088">null</span>) {
                 dispatcher.cleanUpAfterInit();
             }
             init.cleanup();
         }
     }</code></span>

關於封裝的引數filterConfig,首先看下FilterHostConfig ,原始碼如下:

<span style="color:#000000"><code>  <span style="color:#000088">public</span> <span style="color:#000088">class</span> <span style="color:#4f4f4f">FilterHostConfig</span> <span style="color:#000088">implements</span> <span style="color:#4f4f4f">HostConfig</span> {

      <span style="color:#000088">private</span> FilterConfig config;
      <span style="color:#880000">//構造方法</span>
      <span style="color:#000088">public</span> <span style="color:#009900">FilterHostConfig</span>(FilterConfig config) {
          <span style="color:#000088">this</span>.config = config;
      }
      <span style="color:#880000">//根據init-param配置的param-name獲取param-value的值  </span>
      <span style="color:#000088">public</span> String <span style="color:#009900">getInitParameter</span>(String key) {
         <span style="color:#000088">return</span> config.getInitParameter(key);
     }
     <span style="color:#880000">//返回初始化引數名的迭代器 </span>
     <span style="color:#000088">public</span> Iterator<String> <span style="color:#009900">getInitParameterNames</span>() {
         <span style="color:#000088">return</span> MakeIterator.convert(config.getInitParameterNames());
     }
     <span style="color:#880000">//返回Servlet上下文</span>
     <span style="color:#000088">public</span> ServletContext <span style="color:#009900">getServletContext</span>() {
         <span style="color:#000088">return</span> config.getServletContext();
     }
 }</code></span>

只有短短的幾行程式碼,getInitParameterNames是這個類的核心,將Filter初始化引數名稱有列舉型別轉為Iterator。此類的主要作為是對filterConfig 封裝。

接下來,看下StrutsPrepareAndExecuteFilter中init方法中dispatcher = init.initDispatcher(config)。這是初始化dispatcher的,是通過init物件的initDispatcher方法來初始化的。init是InitOperations類的物件,我們看看InitOperations中initDispatcher方法:

<span style="color:#000000"><code><span style="color:#000088">public</span> Dispatcher <span style="color:#009900">initDispatcher</span>( HostConfig filterConfig ) {
         Dispatcher dispatcher = createDispatcher(filterConfig);
         dispatcher.init();
         <span style="color:#000088">return</span> dispatcher;
     }</code></span>

建立Dispatcher,會讀取 filterConfig 中的配置資訊,將配置資訊解析出來,封裝成為一個Map,然後根絕servlet上下文和引數Map構造Dispatcher :

<span style="color:#000000"><code><span style="color:#000088">private</span> Dispatcher createDispatcher( HostConfig filterConfig ) {
          <span style="color:#880000">//存放參數的Map</span>
          <span style="color:#4f4f4f">Map</span><span style="color:#4f4f4f"><</span><span style="color:#4f4f4f">String</span>, <span style="color:#4f4f4f">String</span><span style="color:#4f4f4f">></span> <span style="color:#000088">params</span> <span style="color:#4f4f4f">=</span> <span style="color:#006666">new</span> HashMap<span style="color:#4f4f4f"><</span><span style="color:#4f4f4f">String</span>, <span style="color:#4f4f4f">String</span><span style="color:#4f4f4f">></span>();
          <span style="color:#880000">//將引數存放到Map</span>
          for ( Iterator e <span style="color:#4f4f4f">=</span> filterConfig<span style="color:#4f4f4f">.</span>getInitParameterNames(); e<span style="color:#4f4f4f">.</span>hasNext(); ) {
              <span style="color:#4f4f4f">String</span> name <span style="color:#4f4f4f">=</span> (<span style="color:#4f4f4f">String</span>) e<span style="color:#4f4f4f">.</span>next();
              <span style="color:#4f4f4f">String</span> value <span style="color:#4f4f4f">=</span> filterConfig<span style="color:#4f4f4f">.</span>getInitParameter(name);
              <span style="color:#000088">params</span><span style="color:#4f4f4f">.</span>put(name, value);
          }
         <span style="color:#880000">//根據servlet上下文和引數Map構造Dispatcher </span>
         <span style="color:#000088">return</span> <span style="color:#006666">new</span> Dispatcher(filterConfig<span style="color:#4f4f4f">.</span>getServletContext(), <span style="color:#000088">params</span>);
     }</code></span>

這樣dispatcher物件建立完成,接著就是dispatcher物件的初始化,開啟Dispatcher類,看到它的init方法如下:

<span style="color:#000000"><code> <span style="color:#000088">public</span> <span style="color:#000088">void</span> init() {

         <span style="color:#000088">if</span> (configurationManager == <span style="color:#000088">null</span>) {
             configurationManager = createConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);
         }

         <span style="color:#000088">try</span> {
             init_FileManager();
             <span style="color:#880000">//載入org/apache/struts2/default.properties</span>
            init_DefaultProperties(); <span style="color:#880000">// [1]</span>
            <span style="color:#880000">//載入struts-default.xml,struts-plugin.xml,struts.xml</span>
            init_TraditionalXmlConfigurations(); <span style="color:#880000">// [2]</span>
            init_LegacyStrutsProperties(); <span style="color:#880000">// [3]</span>
            <span style="color:#880000">//使用者自己實現的ConfigurationProviders類 </span>
            init_CustomConfigurationProviders(); <span style="color:#880000">// [5]</span>
            <span style="color:#880000">//Filter的初始化引數 </span>
            init_FilterInitParameters() ; <span style="color:#880000">// [6]</span>
            init_AliasStandardObjects() ; <span style="color:#880000">// [7]</span>

            Container <span style="color:#000088">container</span> = init_PreloadConfiguration();
            <span style="color:#000088">container</span>.inject(<span style="color:#000088">this</span>);
            init_CheckWebLogicWorkaround(<span style="color:#000088">container</span>);

            <span style="color:#000088">if</span> (!dispatcherListeners.isEmpty()) {
                <span style="color:#000088">for</span> (DispatcherListener l : dispatcherListeners) {
                    l.dispatcherInitialized(<span style="color:#000088">this</span>);
                }
            }
        } <span style="color:#000088">catch</span> (Exception ex) {
            <span style="color:#000088">if</span> (LOG.isErrorEnabled())
                LOG.error(<span style="color:#009900">"Dispatcher initialization failed"</span>, ex);
            <span style="color:#000088">throw</span> <span style="color:#000088">new</span> StrutsException(ex);
        }
    }</code></span>

這裡主要是載入一些配置檔案的,將按照順序逐一載入:default.properties,struts-default.xml,struts-plugin.xml,struts.xml,……關於檔案是如何載入的,大家可以自己取看原始檔,主要是由xwork核心類載入的,程式碼在xwork-core\src\main\java\com\opensymphony\xwork2\config\providers包裡面。

現在,我們回到StrutsPrepareAndExecuteFilter類中,剛才我們分析了StrutsPrepareAndExecuteFilter類的init方法,該方法在web容器一啟動就會呼叫的。當用戶訪問某個action的時候,首先呼叫核心過濾器StrutsPrepareAndExecuteFilter的doFilter方法,該方法內容如下:

<span style="color:#000000"><code><span style="color:#000088">public</span> <span style="color:#000088">void</span> <span style="color:#009900">doFilter</span>(ServletRequest req, ServletResponse res, FilterChain chain) <span style="color:#000088">throws</span> IOException, ServletException {

          HttpServletRequest request = (HttpServletRequest) req;
          HttpServletResponse response = (HttpServletResponse) res;

          <span style="color:#000088">try</span> {
              <span style="color:#880000">//設定編碼和國際化</span>
              prepare.setEncodingAndLocale(request, response);
              <span style="color:#880000">//建立action上下文</span>
             prepare.createActionContext(request, response);
             prepare.assignDispatcherToThread();
             <span style="color:#000088">if</span> (excludedPatterns != <span style="color:#000088">null</span> && prepare.isUrlExcluded(request, excludedPatterns)) {
                 chain.doFilter(request, response);
             } <span style="color:#000088">else</span> {
                 request = prepare.wrapRequest(request);
                 ActionMapping mapping = prepare.findActionMapping(request, response, <span style="color:#000088">true</span>);
                 <span style="color:#880000">//如果mapping為空,則認為不是呼叫action,會呼叫下一個過濾器鏈,直到獲取到mapping才呼叫action</span>
                 <span style="color:#000088">if</span> (mapping == <span style="color:#000088">null</span>) {
                     <span style="color:#000088">boolean</span> handled = execute.executeStaticResourceRequest(request, response);
                     <span style="color:#000088">if</span> (!handled) {
                         chain.doFilter(request, response);
                     }
                 } <span style="color:#000088">else</span> {
                     <span style="color:#880000">//執行action</span>
                     execute.executeAction(request, response, mapping);
                 }
             }
         } <span style="color:#000088">finally</span> {
             prepare.cleanupRequest(request);
         }
     }</code></span>

下面對doFilter方法中的重點部分一一講解:

  1. prepare.setEncodingAndLocale(request, response) 
    第8行是呼叫prepare物件的setEncodingAndLocale方法,prepare是PrepareOperations類的物件,PrepareOperations類是用來做請求準備工作的。我們看下PrepareOperations類中的setEncodingAndLocale方法:
<span style="color:#000000"><code><span style="color:#000088">public</span> void setEncodingAndLocale(HttpServletRequest <span style="color:#4f4f4f">request</span>, HttpServletResponse <span style="color:#4f4f4f">response</span>) {
         dispatcher.prepare(<span style="color:#4f4f4f">request</span>, <span style="color:#4f4f4f">response</span>);
     }</code></span>

在這方法裡面我們可以看到它只是呼叫了dispatcher的prepare方法而已,下面我們看看dispatcher的prepare方法:

<span style="color:#000000"><code>public void prepare(HttpServletRequest request, HttpServletResponse response) {
          String encoding = <span style="color:#006666">null</span>;
          <span style="color:#000088">if</span> (defaultEncoding != <span style="color:#006666">null</span>) {
              encoding = defaultEncoding;
          }
          <span style="color:#008800">//</span> check <span style="color:#000088">for</span> Ajax request to use UTF-<span style="color:#006666">8</span> encoding strictly <span style="color:#009900">http</span>:<span style="color:#008800">//</span>www.w3.org<span style="color:#008800">/TR/XMLHttpRequest/</span><span style="color:#880000">#the-send-method</span>
          <span style="color:#000088">if</span> (<span style="color:#009900">"XMLHttpRequest"</span>.equals(request.getHeader(<span style="color:#009900">"X-Requested-With"</span>))) {
              encoding = <span style="color:#009900">"UTF-8"</span>;
          }

         Locale locale = <span style="color:#006666">null</span>;
         <span style="color:#000088">if</span> (defaultLocale != <span style="color:#006666">null</span>) {
             locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
         }

         <span style="color:#000088">if</span> (encoding != <span style="color:#006666">null</span>) {
             applyEncoding(request, encoding);
         }

         <span style="color:#000088">if</span> (locale != <span style="color:#006666">null</span>) {
             response.setLocale(locale);
         }

         <span style="color:#000088">if</span> (paramsWorkaroundEnabled) {
             request.getParameter(<span style="color:#009900">"foo"</span>); <span style="color:#008800">//</span> simply read any parameter (existing <span style="color:#000088">or</span> <span style="color:#000088">not</span>) to <span style="color:#009900">"prime"</span> the request
         }
     }</code></span>

我們可以看到該方法只是簡單的設定了encoding 和locale ,做的只是一些輔助的工作。

  1. prepare.createActionContext(request, response)

我們回到StrutsPrepareAndExecuteFilter的doFilter方法,看到第10行程式碼:prepare.createActionContext(request, response);這是action上下文的建立。ActionContext是一個容器,這個容器主要儲存request、session、application、parameters等相關信 息。ActionContext是一個執行緒的本地變數,這意味著不同的action之間不會共享ActionContext,所以也不用考慮執行緒安全問 題。其實質是一個Map,key是標示request、session、……的字串,值是其對應的物件,我們可以看到com.opensymphony.xwork2.ActionContext類中時如下定義的:

<span style="color:#000000"><code> <span style="color:#000088">static</span> ThreadLocal<ActionContext> actionContext = <span style="color:#000088">new</span> ThreadLocal<ActionContext>();</code></span>
  •  

我們看下PrepareOperations類的createActionContext方法:

<span style="color:#000000"><code> <span style="color:#880000">/**
       * Creates the action context and initializes the thread local
       */</span>
      <span style="color:#000088">public</span> ActionContext <span style="color:#009900">createActionContext</span>(HttpServletRequest request, HttpServletResponse response) {
          ActionContext ctx;
          Integer counter = <span style="color:#006666">1</span>;
          Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
          <span style="color:#000088">if</span> (oldCounter != <span style="color:#000088">null</span>) {
              counter = oldCounter + <span style="color:#006666">1</span>;
         }
         <span style="color:#880000">//此處是從ThreadLocal中獲取此ActionContext變數</span>
         ActionContext oldContext = ActionContext.getContext();
         <span style="color:#000088">if</span> (oldContext != <span style="color:#000088">null</span>) {
             <span style="color:#880000">// detected existing context, so we are probably in a forward</span>
             ctx = <span style="color:#000088">new</span> ActionContext(<span style="color:#000088">new</span> HashMap<String, Object>(oldContext.getContextMap()));
         } <span style="color:#000088">else</span> {
             ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
             stack.getContext().putAll(dispatcher.createContextMap(request, response, <span style="color:#000088">null</span>, servletContext));
             <span style="color:#880000">//stack.getContext()返回的是一個Map<String,Object>,根據此Map構造一個ActionContext</span>
             ctx = <span style="color:#000088">new</span> ActionContext(stack.getContext());
         }
         request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
         <span style="color:#880000">//將ActionContext存到ThreadLocal </span>
         ActionContext.setContext(ctx);
         <span style="color:#000088">return</span> ctx;
     }</code></span>

上面第18行程式碼中dispatcher.createContextMap,如何封裝相關引數:

<span style="color:#000000"><code>  <span style="color:#000088">public</span> <span style="color:#4f4f4f">Map</span><span style="color:#4f4f4f"><</span><span style="color:#4f4f4f">String</span>,Object<span style="color:#4f4f4f">></span> createContextMap(HttpServletRequest request, HttpServletResponse response,
              ActionMapping mapping, ServletContext context) {

          <span style="color:#880000">// request map wrapping the http request objects</span>
          <span style="color:#4f4f4f">Map</span> requestMap <span style="color:#4f4f4f">=</span> <span style="color:#006666">new</span> RequestMap(request);

          <span style="color:#880000">// parameters map wrapping the http parameters.  ActionMapping parameters are now handled and applied separately</span>
          <span style="color:#4f4f4f">Map</span> <span style="color:#000088">params</span> <span style="color:#4f4f4f">=</span> <span style="color:#006666">new</span> HashMap(request<span style="color:#4f4f4f">.</span>getParameterMap());

         <span style="color:#880000">// session map wrapping the http session</span>
         <span style="color:#4f4f4f">Map</span> session <span style="color:#4f4f4f">=</span> <span style="color:#006666">new</span> SessionMap(request);

         <span style="color:#880000">// application map wrapping the ServletContext</span>
         <span style="color:#4f4f4f">Map</span> application <span style="color:#4f4f4f">=</span> <span style="color:#006666">new</span> ApplicationMap(context);
         <span style="color:#880000">//requestMap、params、session等Map封裝成為一個上下文Map </span>
         <span style="color:#4f4f4f">Map</span><span style="color:#4f4f4f"><</span><span style="color:#4f4f4f">String</span>,Object<span style="color:#4f4f4f">></span> extraContext <span style="color:#4f4f4f">=</span> createContextMap(requestMap, <span style="color:#000088">params</span>, session, application, request, response, context);

         <span style="color:#000088">if</span> (mapping <span style="color:#4f4f4f">!=</span> <span style="color:#4f4f4f">null</span>) {
             extraContext<span style="color:#4f4f4f">.</span>put(ServletActionContext<span style="color:#4f4f4f">.</span>ACTION_MAPPING, mapping);
         }
         <span style="color:#000088">return</span> extraContext;
     }</code></span>
  1. request = prepare.wrapRequest(request) 
    我們再次回到StrutsPrepareAndExecuteFilter的doFilter方法中,看到第15行:request = prepare.wrapRequest(request);這一句是對request進行包裝的,我們看下prepare的wrapRequest方法: 
      
<span style="color:#000000"><code><span style="color:#000088">public</span> HttpServletRequest <span style="color:#009900">wrapRequest</span>(HttpServletRequest oldRequest) <span style="color:#000088">throws</span> ServletException {
          HttpServletRequest request = oldRequest;
          <span style="color:#000088">try</span> {
              <span style="color:#880000">// Wrap request first, just in case it is multipart/form-data</span>
              <span style="color:#880000">// parameters might not be accessible through before encoding (ww-1278)</span>
              request = dispatcher.wrapRequest(request, servletContext);
          } <span style="color:#000088">catch</span> (IOException e) {
              <span style="color:#000088">throw</span> <span style="color:#000088">new</span> ServletException(<span style="color:#009900">"Could not wrap servlet request with MultipartRequestWrapper!"</span>, e);
          }
         <span style="color:#000088">return</span> request;
     }</code></span>

由第6行我們可以看到它裡面呼叫的是dispatcher的wrapRequest方法,並且將servletContext物件也傳進去了,我們看下dispatcher的wrapRequest:

 此次包裝根據請求內容的型別不同,返回不同的物件,如果為multipart/form-data型別,則返回MultiPartRequestWrapper型別的物件,該物件服務於檔案上傳,否則返回StrutsRequestWrapper型別的物件,MultiPartRequestWrapper是StrutsRequestWrapper的子類,而這兩個類都是HttpServletRequest介面的實現。

  1. ActionMapping mapping = prepare.findActionMapping(request, response, true)

包裝request後,通過ActionMapper的getMapping()方法得到請求的Action,Action的配置資訊儲存在ActionMapping物件中,如StrutsPrepareAndExecuteFilter的doFilter方法中第16行:ActionMapping mapping = prepare.findActionMapping(request, response, true);我們找到prepare物件的findActionMapping方法:

<span style="color:#000000"><code> <span style="color:#000088">public</span> ActionMapping <span style="color:#009900">findActionMapping</span>(HttpServletRequest request, HttpServletResponse response, <span style="color:#000088">boolean</span> forceLookup) {
          <span style="color:#880000">//首先從request物件中取mapping物件,看是否存在</span>
          ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
          <span style="color:#880000">//不存在就建立一個</span>
          <span style="color:#000088">if</span> (mapping == <span style="color:#000088">null</span> || forceLookup) {
              <span style="color:#000088">try</span> {
                  <span style="color:#880000">//首先建立ActionMapper物件,通過ActionMapper物件建立mapping物件</span>
                  mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
                  <span style="color:#000088">if</span> (mapping != <span style="color:#000088">null</span>) {
                     request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
                 }
             } <span style="color:#000088">catch</span> (Exception ex) {
                 dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
             }
         }

         <span style="color:#000088">return</span> mapping;
     }</code></span>

下面是ActionMapper介面的實現類DefaultActionMapper的getMapping()方法的原始碼:

<span style="color:#000000"><code>  <span style="color:#000088">public</span> ActionMapping <span style="color:#009900">getMapping</span>(HttpServletRequest request, ConfigurationManager configManager) {
          ActionMapping mapping = <span style="color:#000088">new</span> ActionMapping();
          <span style="color:#880000">//獲得請求的uri,即請求路徑URL中工程名以後的部分,如/userAction.action</span>
          String uri = getUri(request);
          <span style="color:#880000">//修正url的帶;jsessionid 時找不到的bug</span>
          <span style="color:#000088">int</span> indexOfSemicolon = uri.indexOf(<span style="color:#009900">";"</span>);
          uri = (indexOfSemicolon > -<span style="color:#006666">1</span>) ? uri.substring(<span style="color:#006666">0</span>, indexOfSemicolon) : uri;
          <span style="color:#880000">//刪除副檔名,如.action或者.do</span>
          uri = dropExtension(uri, mapping);
         <span style="color:#000088">if</span> (uri == <span style="color:#000088">null</span>) {
             <span style="color:#000088">return</span> <span style="color:#000088">null</span>;
         }
         <span style="color:#880000">//從uri中分離得到請求的action名、名稱空間。</span>
         parseNameAndNamespace(uri, mapping, configManager);
         <span style="color:#880000">//處理特殊的請求引數,將這些引數儲存在一個HashSet中</span>
         handleSpecialParameters(request, mapping);
         <span style="color:#880000">//如果允許動態方法呼叫,即形如/userAction!getAll.action的請求,分離action名和方法名</span>
         <span style="color:#000088">return</span> parseActionName(mapping);
     }</code></span>

可以看到,該方法從請求的uri中分離出name和namespace並存放在mapping中。並對請求action名解析,分離出action和方法名。 
5. execute.executeAction(request, response, mapping) 
上面我們分析完了mapping的獲取,繼續看doFilter方法:

<span style="color:#000000"><code><span style="color:#880000">//如果mapping為空,則認為不是呼叫action,會呼叫下一個過濾器鏈,直到獲取到mapping才呼叫action</span>
        <span style="color:#000088">if</span> (mapping == <span style="color:#000088">null</span>) {
            <span style="color:#000088">boolean</span> handled = execute.executeStaticResourceRequest(request, response);
            <span style="color:#000088">if</span> (!handled) {
                chain.doFilter(request, response);
            }
        } <span style="color:#000088">else</span> {
            <span style="color:#880000">//執行action</span>
            execute.executeAction(request, response, mapping);
        }</code></span>

如果mapping物件不為空,則會執行action,我們看到上面程式碼第9行:execute是ExecuteOperations類的物件,ExecuteOperations類在包org.apache.struts2.dispatcher.ng下面,我們找到它裡面的executeAction方法:

<span style="color:#000000"><code> <span style="color:#000088">public</span> <span style="color:#000088">void</span> <span style="color:#009900">executeAction</span>(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) <span style="color:#000088">throws</span> ServletException {
         dispatcher.serviceAction(request, response, servletContext, mapping);
     }</code></span>

我們可以看到它裡面只是簡單的呼叫了dispatcher的serviceAction方法:我們找到dispatcher的serviceAction方法:

<span style="color:#000000"><code><span style="color:#000088">public</span> <span style="color:#006666">void</span> serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
                              ActionMapping mapping) throws ServletException {
        <span style="color:#880000">//封轉上下文環境,主要將requestMap、params、session等Map封裝成為一個上下文Map</span>
        <span style="color:#4f4f4f">Map</span><span style="color:#4f4f4f"><</span><span style="color:#4f4f4f">String</span>, Object<span style="color:#4f4f4f">></span> extraContext <span style="color:#4f4f4f">=</span> createContextMap(request, response, mapping, context);

        <span style="color:#880000">// If there was a previous value stack, then create a new copy and pass it in to be used by the new Action</span>
        ValueStack <span style="color:#4f4f4f">stack</span> <span style="color:#4f4f4f">=</span> (ValueStack) request<span style="color:#4f4f4f">.</span>getAttribute(ServletActionContext<span style="color:#4f4f4f">.</span>STRUTS_VALUESTACK_KEY);
        boolean nullStack <span style="color:#4f4f4f">=</span> <span style="color:#4f4f4f">stack</span> <span style="color:#4f4f4f">==</span> <span style="color:#4f4f4f">null</span>;
        <span style="color:#000088">if</span> (nullStack) {
            ActionContext ctx <span style="color:#4f4f4f">=</span> ActionContext<span style="color:#4f4f4f">.</span>getContext();
            <span style="color:#000088">if</span> (ctx <span style="color:#4f4f4f">!=</span> <span style="color:#4f4f4f">null</span>) {
                <span style="color:#4f4f4f">stack</span> <span style="color:#4f4f4f">=</span> ctx<span style="color:#4f4f4f">.</span>getValueStack();
            }
        }
        <span style="color:#000088">if</span> (<span style="color:#4f4f4f">stack</span> <span style="color:#4f4f4f">!=</span> <span style="color:#4f4f4f">null</span>) {
            extraContext<span style="color:#4f4f4f">.</span>put(ActionContext<span style="color:#4f4f4f">.</span>VALUE_STACK, valueStackFactory<span style="color:#4f4f4f">.</span>createValueStack(<span style="color:#4f4f4f">stack</span>));
        }

        <span style="color:#4f4f4f">String</span> timerKey <span style="color:#4f4f4f">=</span> <span style="color:#009900">"Handling request from Dispatcher"</span>;
        try {
            UtilTimerStack<span style="color:#4f4f4f">.</span>push(timerKey);
            <span style="color:#4f4f4f">String</span> namespace <span style="color:#4f4f4f">=</span> mapping<span style="color:#4f4f4f">.</span>getNamespace();<span style="color:#880000">//從mapping物件獲取名稱空間</span>
            <span style="color:#4f4f4f">String</span> name <span style="color:#4f4f4f">=</span> mapping<span style="color:#4f4f4f">.</span>getName();          <span style="color:#880000">//獲取請求的action名</span>
            <span style="color:#4f4f4f">String</span> method <span style="color:#4f4f4f">=</span> mapping<span style="color:#4f4f4f">.</span>getMethod();      <span style="color:#880000">//獲取請求方法</span>
            <span style="color:#880000">//得到配置物件</span>
            Configuration config <span style="color:#4f4f4f">=</span> configurationManager<span style="color:#4f4f4f">.</span>getConfiguration();
            <span style="color:#880000">//根據執行上下文引數,名稱空間,名稱等建立使用者自定義Action的代理物件  </span>
            ActionProxy proxy <span style="color:#4f4f4f">=</span> config<span style="color:#4f4f4f">.</span>getContainer()<span style="color:#4f4f4f">.</span>getInstance(ActionProxyFactory<span style="color:#4f4f4f">.</span>class)<span style="color:#4f4f4f">.</span>createActionProxy(
                    namespace, name, method, extraContext, <span style="color:#006666">true</span>, <span style="color:#006666">false</span>);

            request<span style="color:#4f4f4f">.</span>setAttribute(ServletActionContext<span style="color:#4f4f4f">.</span>STRUTS_VALUESTACK_KEY, proxy<span style="color:#4f4f4f">.</span>getInvocation()<span style="color:#4f4f4f">.</span>getStack());

            <span style="color:#880000">// if the ActionMapping says to go straight to a result, do it!</span>
            <span style="color:#880000">//如果配置檔案中執行的這個action配置了result,就直接轉到result</span>
            <span style="color:#000088">if</span> (mapping<span style="color:#4f4f4f">.</span>getResult() <span style="color:#4f4f4f">!=</span> <span style="color:#4f4f4f">null</span>) {
                Result result <span style="color:#4f4f4f">=</span> mapping<span style="color:#4f4f4f">.</span>getResult();
                result<span style="color:#4f4f4f">.</span>execute(proxy<span style="color:#4f4f4f">.</span>getInvocation());
            } <span style="color:#000088">else</span> {
                proxy<span style="color:#4f4f4f">.</span>execute();
            }

            <span style="color:#880000">// If there was a previous value stack then set it back onto the request</span>
            <span style="color:#000088">if</span> (<span style="color:#4f4f4f">!</span>nullStack) {
                request<span style="color:#4f4f4f">.</span>setAttribute(ServletActionContext<span style="color:#4f4f4f">.</span>STRUTS_VALUESTACK_KEY, <span style="color:#4f4f4f">stack</span>);
            }
        } catch (ConfigurationException e) {
            <span style="color:#880000">// WW-2874 Only log error if in devMode</span>
            <span style="color:#000088">if</span> (devMode) {
                <span style="color:#4f4f4f">String</span> reqStr <span style="color:#4f4f4f">=</span> request<span style="color:#4f4f4f">.</span>getRequestURI();
                <span style="color:#000088">if</span> (request<span style="color:#4f4f4f">.</span>getQueryString() <span style="color:#4f4f4f">!=</span> <span style="color:#4f4f4f">null</span>) {
                    reqStr <span style="color:#4f4f4f">=</span> reqStr <span style="color:#4f4f4f">+</span> <span style="color:#009900">"?"</span> <span style="color:#4f4f4f">+</span> request<span style="color:#4f4f4f">.</span>getQueryString();
                }
                <span style="color:#000088">LOG</span><span style="color:#4f4f4f">.</span>error(<span style="color:#009900">"Could not find action or result\n"</span> <span style="color:#4f4f4f">+</span> reqStr, e);
            } <span style="color:#000088">else</span> {
                <span style="color:#000088">if</span> (<span style="color:#000088">LOG</span><span style="color:#4f4f4f">.</span>isWarnEnabled()) {
                    <span style="color:#000088">LOG</span><span style="color:#4f4f4f">.</span>warn(<span style="color:#009900">"Could not find action or result"</span>, e);
                }
            }
            sendError(request, response, context, HttpServletResponse<span style="color:#4f4f4f">.</span>SC_NOT_FOUND, e);
        } catch (Exception e) {
            <span style="color:#000088">if</span> (handleException <span style="color:#4f4f4f">||</span> devMode) {
                sendError(request, response, context, HttpServletResponse<span style="color:#4f4f4f">.</span>SC_INTERNAL_SERVER_ERROR, e);
            } <span style="color:#000088">else</span> {
                throw <span style="color:#006666">new</span> ServletException(e);
            }
        } finally {
            UtilTimerStack<span style="color:#4f4f4f">.</span>pop(timerKey);
        }
    }</code></span>

核心程式碼就是根據前面的請求準備工作,為使用者建立ActionProxy物件,執行相應的action,若配置有執行結果,那麼返回執行結果。