Struts2 內核之我見

相信熟悉 Struts1 的程序員,對 Struts2 會迷惑,凡事是是而非。我也曾經遇到了這種狀況。Struts2 在設計的時候採用 webwork 的內核,儘可能按照 Struts1 的編碼習慣。css

我不知道各位怎麼學習 Struts1,當我閱讀了核心控制器 org.apache.struts.action.ActionServlet 的源碼後,感到對 Struts1 的工做機制豁然開朗。 Struts2 一樣也是 MVC 框架,但核心控制器是過濾器 org.apache.struts2.dispatcher.FilterDispatcher。java

感謝網上對 Struts2 工做機制研究而且願意跟你們分享的熱心人,我在學習 Struts2 的時候獲得不少幫助。若是你不是頗有經驗的程序員,我說的不少東西你可能馬上理解不了。若是有時間,我會作成 ppt,也但願給你們講解,共同交流進步。程序員

若是你須要親自動手實踐,學習源碼,請下載如下的 2 個 jar 包。web

圖 1. 官方源碼包

官方源碼包

工做流程的官方描述

咱們從看官方的流程圖開始。當本篇文章結束的時候,咱們會再一遍來看它。apache

圖 2. 官方工做流程圖

官方工做流程圖

  1. 初始的請求經過一條標準的過濾器鏈,到達 servlet 容器 ( 好比 tomcat 容器,WebSphere 容器 )。設計模式

  2. 過濾器鏈包括可選的 ActionContextCleanUp 過濾器,用於系統整合技術,如 SiteMesh 插件。tomcat

  3. 接着調用 FilterDispatcher,FilterDispatcher 查找 ActionMapper,以肯定這個請求是否須要調用某個 Action。session

  4. 若是 ActionMapper 肯定須要調用某個 Action,FilterDispatcher 將控制權交給 ActionProxy。數據結構

  5. ActionProxy 依照框架的配置文件(struts.xml),找到須要調用的 Action 類。app

  6. ActionProxy 建立一個 ActionInvocation 的實例。ActionInvocation 先調用相關的攔截器 (Action 調用以前的部分),最後調用 Action。

  7. 一旦 Action 調用返回結果,ActionInvocation 根據 struts.xml 配置文件,查找對應的轉發路徑。返回結果一般是(但不老是,也多是另外的一個 Action 鏈)JSP 技術或者 FreeMarker 的模版技術的網頁呈現。Struts2 的標籤和其餘視圖層組件,幫助呈現咱們所須要的顯示結果。在此,我想說清楚一些,最終的顯示結果必定是 HTML 標籤。標籤庫技術和其餘視圖層技術只是爲了動態生成 HTML 標籤。

  8. 接着按照相反次序執行攔截器鏈 ( 執行 Action 調用以後的部分 )。最後,響應經過濾器鏈返回(過濾器技術執行流程與攔截器同樣,都是先執行前面部分,後執行後面部)。若是過濾器鏈中存在 ActionContextCleanUp,FilterDispatcher 不會清理線程局部的 ActionContext。若是不存在 ActionContextCleanUp 過濾器,FilterDispatcher 會清除全部線程局部變量。

備註:攔截和過濾器的執行順序可能一些人理解不了,我以生活中的範例說明。我去上海的 IBM 實驗室出差,火車沿途停靠蚌埠,南京,最終達到上海。辦完事情後回來,沿途的停靠站是南京、蚌埠。有沒有注意到火車停靠站的順序相反了。好,轉到咱們遇到的技術問題,上海的業務至關於 Action 執行,是調用的真正目標。蚌埠和南京是兩個分別的過濾器。即便我兩次路過南京,只是一個過濾器的調用先執行一半後執行一半罷了。

回頁首

核心控制器 org.apache.struts2.dispatcher.FilterDispatcher

filter 是否能夠做爲控制器

傳統的 Java MVC 設計模式,控制器自然是 servlet。也許有人說,沒有 servlet 還叫 MVC 結構嗎?對 filter 做爲控制器表示懷疑。filter 爲何不能夠作控制器,動態網頁也能夠作控制器?我不知道若是你開發 PHP 項目,MVC 你怎麼處理的,可是我認爲是確定的。

請看下面的例子,過濾器實現控制器。核心方法 doFilter 的處理有 3 個出口。

  1. 請求資源以 .action 結尾,進行 action 處理

  2. 對樣式表的直接訪問。如地址欄直接輸入網址 /css/main.css 將會被拒絕

  3. 其它資源請求,不處理請求直接經過

清單 1. 過濾器用做控制器
 class FilterDispatcher implements Filter { 
    private FilterConfig filterConfig; 
	
    public void init(FilterConfig filterConfig) throws ServletException {} 
	
    public void destroy() {} 

    // 核心過濾方法
    public void doFilter(ServletRequest request, 
            ServletResponse response, FilterChain filterChain) 
            throws IOException, ServletException { 
        HttpServletRequest req = (HttpServletRequest) request; 
        HttpServletResponse res = (HttpServletResponse) response; 
        String uri = req.getRequestURI(); 
      
         // 1 action 請求
	 // 可能的 uri 形式爲 / 站點名 /resourceName/ 可選路徑 /Product_input.action 
        if (uri.endsWith(".action")) { 
            int lastIndex = uri.lastIndexOf("/"); 
			  //1.1 處理 action 結尾的請求
            String action = uri.substring(lastIndex + 1); 
            if (action.equals("Product_input.action")) { 
                //1.1.1 請求商品輸入不作處理
            } else if (action.equals("Product_save.action")) { 
                Product product = new Product(); 
                //1.1.2 保存商品信息
                product.setProductName(request.getParameter("productName")); 
                product.setDescription(request.getParameter("description")); 
                product.setPrice(request.getParameter("price")); 
                product.save(); 
                request.setAttribute("product", product); 
            } 

            //1.2 轉向視圖
            String dispatchUrl = null; 
            if (action.equals("Product_input.action")) { 
                dispatchUrl = "/jsp/ProductForm.jsp"; 
            } else if (action.equals("Product_save.action")) { 
                dispatchUrl = "/jsp/ProductDetails.jsp"; 
            } 
            if (dispatchUrl != null) { 
                RequestDispatcher rd = request 
                        .getRequestDispatcher(dispatchUrl); 
                rd.forward(request, response); 
            } 
        } else if (uri.indexOf("/css/") != -1 
                && req.getHeader("referer") == null) { 
           //2 拒絕對樣式表的直接訪問
            res.sendError(HttpServletResponse.SC_FORBIDDEN); 
        } else { 
	 //3 請求其餘資源,經過過濾器
            filterChain.doFilter(request, response); 
        } 
    } 
 }

FilterDispatcher 的工做流程

前面講過 Struts2 的核心控制器爲 filter,對於一個控制器,核心的生命週期方法有 3 個。

清單 2. 過濾器生命週期方法
 // 初始化,加載資源
 public void init(FilterConfig filterConfig) throws ServletException 
 // 銷燬,回收資源
 public void destroy() 
 // 過濾
 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
 throws IOException, ServletException

分別講解 FilterDispatcher 3 個方法

init 方法:初始化過濾器,建立默認的 dispatcher 對象而且設置靜態資源的包。

清單 3. init 方法
 public void init(FilterConfig filterConfig) throws ServletException { 
	 try { 
		 this.filterConfig = filterConfig; 
			 // 初始化日誌器
		 initLogging(); 

		 dispatcher = createDispatcher(filterConfig); 
		 dispatcher.init(); 
		 dispatcher.getContainer().inject(this); 

		 staticResourceLoader.setHostConfig(new FilterHostConfig(filterConfig)); 
	 } finally { 
		 ActionContext.setContext(null); 
	 } 
 }

destory 方法:核心業務是調用 dispatcher.cleanup() 方法。cleanup 釋放全部綁定到 dispatcher 實例的資源,包括銷燬全部的攔截器實例,本方法在後面有源代碼討論。

清單 4. destory 方法
 public void destroy() { 
	 if (dispatcher == null) { 
    log.warn("something is seriously wrong, Dispatcher is not initialized (null) ");
	 } else { 
		 try { 
			 dispatcher.cleanup(); 
		 } finally { 
			 ActionContext.setContext(null); 
		 } 
	 } 
 }

doFilter 方法:doFilter 方法的出口有 3 個分支。
首先過濾器嘗試把 request 匹配到一個 Action mapping(action mapping 的解釋見最後的總結)。如有匹配,執行 path1。不然執行 path2 或者 3。
path 1調用 dispatcher. serviceAction() 方法處理 Action 請求 
若是找到了 mapping,Action 處理被委託給 dispatcher 的 serviceAction 方法。 若是 Action 處理失敗了,doFilter 將會經過 dispatcher 建立一個錯誤頁。
path 2處理靜態資源 
若是請求的是靜態資源。資源被直接拷貝到 response 對象,同時設置對應的頭信息。
path 3無處理直接經過過濾器,訪問過濾器鏈的下個資源。

清單 5. doFilter 方法
 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
				 throws IOException, ServletException { 
	 HttpServletRequest request = (HttpServletRequest) req; 
	 HttpServletResponse response = (HttpServletResponse) res; 
	 ServletContext servletContext = getServletContext(); 

	 String timerKey = "FilterDispatcher_doFilter: "; 
	 try { 
		 //1 處理前的準備
		 //1.1 建立值棧對象,值棧包含 object stack 和 context map 兩個部分。
ValueStack stack = 
dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); 
        //1.2 建立 actionContext。
	  ActionContext ctx = new ActionContext(stack.getContext()); 
		 ActionContext.setContext(ctx); 
		 UtilTimerStack.push(timerKey); 

		 //1.3 準備和包裝 request 
		 request = prepareDispatcherAndWrapRequest(request, response); 
		 ActionMapping mapping; 

		 //2 根據請求路徑查找 actionMapping 
		 try { 
   mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager()); 
		 } catch (Exception ex) { 
			 log.error("error getting ActionMapping", ex); 
			 dispatcher.sendError(request, response, servletContext, 
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); 
			 return; 
		 } 
		 //3 當請求路徑沒有對應的 actionMapping,走第 2 和第 3 個出口
		 if (mapping == null) { 
			 String resourcePath = RequestUtils.getServletPath(request); 

			 if ("".equals(resourcePath) && null != request.getPathInfo()) { 
				 resourcePath = request.getPathInfo(); 
			 } 

			 if (staticResourceLoader.canHandle(resourcePath)) { 
      staticResourceLoader.findStaticResource(resourcePath, request, response);   
			 } else { 
				 chain.doFilter(request, response); 
			 } 
			 // 若是是第 2 和第 3 個出口 ,action 的處理到此結束。
			 return; 
		 } 
		 //3.1 路徑 1,委託 dispatcher 的 serviceAction 進行處理
		 dispatcher.serviceAction(request, response, servletContext, mapping); 

	 } finally { 
		 try { 
		   //4 清除 ActionContext 
			 ActionContextCleanUp.cleanUp(req); 
		 } finally { 
			 UtilTimerStack.pop(timerKey); 
		 } 
	 } 
 }

對 doFilter() 方法的幾點說明 :

  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 做用域。

    圖 3. 值棧

    值棧

  3. 準備和包裝 request,包含 2 步 :
    準備請求,設置區域和字符編碼 
    包裝 request,若是包含文件上傳,則網頁表單設置爲 multipart/form-data,返回 MultiPartRequestWrapper 類型。 
    若是普通表單提交,返回 StrutsRequestWrapper。這個類是 multipart/form-data 的父類。

  4. ActionMapping 表明 struts.xml 文件中的一個 Action 配置,被傳入到 serviceAction 中。注意 ActionMapping 不表明 Action 集合,只表明某個對應的 Action。

    清單 6. action 配置
     <action name="Pay" class=" "> 
    	 <interceptor-ref name="tokenSession" / > 
    	 <interceptor-ref name="basicStack" / > 
    	 <result name="input">/jsp/Payment.jsp</result> 
    	 <result>/jsp/Thanks.jsp</result> 
     </action>
  5. 若是是一個 Action 請求,( 請求路徑在 struts.xml 有對應的 Action 配置 ),則調用 dispatcher.serviceAction() 處理。

  6. finally 語句塊中調用 ActionContextCleanUp.cleanUp(req),以清除 ActionContext。

下邊,咱們將討論 dispatcher 類。

org.apache.struts2.dispatcher.Dispatcher

Dispatcher 作爲實際派發器的工具類,委派大部分的處理任務。核心控制器持有一個本類實例,爲全部的請求所共享。 本部分分析了兩個重要方法。
serviceAction():加載 Action 類,調用 Action 類的方法,轉向到響應結果。響應結果指代碼清單 5 中 <result/> 標籤所表明的對象。
cleanup():釋放全部綁定到 dispatcher 實例的資源。

serviceAction 方法

根據 action Mapping 加載 Action 類,調用對應的 Action 方法,轉向相應結果。
首先,本方法根據給定參數,建立 Action context。接着,根據 Action 的名稱和命名空間,建立 Action 代理。( 注意這代理模式中的代理角色 ) 而後,調用代理的 execute() 方法,輸出相應結果。
若是 Action 或者 result 沒有找到,將經過 sendError() 報 404 錯誤。

清單 7. serviceAction 方法
 public void serviceAction(HttpServletRequest request, HttpServletResponse response, 
          ServletContext context,  ActionMapping mapping) throws ServletException {
         Map<String, Object> extraContext = createContextMap 
					 (request, response, mapping, context); 

        //1 如下代碼目的爲獲取 ValueStack,代理在調用的時候使用的是本值棧的副本
        ValueStack stack = (ValueStack) request.getAttribute 
					 (ServletActionContext.STRUTS_VALUESTACK_KEY); 
        boolean nullStack = stack == null; 
        if (nullStack) { 
            ActionContext ctx = ActionContext.getContext(); 
            if (ctx != null) { 
                stack = ctx.getValueStack(); 
            } 
        } 
       //2 建立 ValueStack 的副本
        if (stack != null) { 
            extraContext.put(ActionContext.VALUE_STACK, 
					 valueStackFactory.createValueStack(stack)); 
        } 
        String timerKey = "Handling request from Dispatcher"; 
        try { 
            UtilTimerStack.push(timerKey); 
        //3 這個是獲取配置文件中 <action/> 配置的字符串,action 對象已經在覈心控制器中建立
            String namespace = mapping.getNamespace(); 
            String name = mapping.getName(); 
            String method = mapping.getMethod(); 
            // xwork 的配置信息
            Configuration config = configurationManager.getConfiguration(); 

            //4 動態建立 ActionProxy 
ActionProxy proxy = 
config.getContainer().getInstance(ActionProxyFactory.class).
createActionProxy(namespace, name, method, extraContext, true, false); 
			
            request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, 
					 proxy.getInvocation().getStack()); 

           
            //5 調用代理
            if (mapping.getResult() != null) { 
                Result result = mapping.getResult(); 
                result.execute(proxy.getInvocation()); 
            } else { 
                proxy.execute(); 
            } 

            //6 處理結束後,恢復值棧的代理調用前狀態
            if (!nullStack) { 
                request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); 
            } 
        } catch (ConfigurationException e) { 
        	 //7 若是 action 或者 result 沒有找到,調用 sendError 報 404 錯誤
        	 if(devMode) { 
        		 LOG.error("Could not find action or result", e); 
        	 } 
        	 else { 
        		 LOG.warn("Could not find action or result", e); 
        	 } 
            sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e); 
        } catch (Exception e) { 
            sendError(request, response, context,
              HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
              } finally { 
            UtilTimerStack.pop(timerKey); 
        } 
 }

幾點說明:

  1. Valuestack 對象保存在 request 裏,對應的 key 是 ServletActionContext.STRUTS_VALUESTACK_KEY。調用代理以前首先建立 Valuestack 副本,調用代理時使用副本,調用後使用原實例恢復。本處的值棧指 object stack。

  2. Dispatcher 實例,建立一個 Action 代理對象。並把處理委託給代理對象的 execute 方法。

  3. 若是你不懂代理模式,那麼在下一部分,我會簡單介紹這種模式。但本處使用的是動態代理。動態代理,指代理類和代理對象都是動態生成的,不須要編寫類的源代碼。

  4. createContextMap 的建立,建議去看一下。代碼很簡單。

cleanup 方法

釋放全部綁定到 dispatcher 實例的資源

清單 8. cleanup 方法
 public void cleanup() { 
	 //1 銷燬 ObjectFactory 
	 ObjectFactory objectFactory = getContainer().getInstance(ObjectFactory.class); 
	 if (objectFactory == null) { 
		 LOG.warn("Object Factory is null, something is seriously wrong, 
					 no clean up will be performed"); 
	 } 
	 if (objectFactory instanceof ObjectFactoryDestroyable) { 
		 try { 
			 ((ObjectFactoryDestroyable)objectFactory).destroy(); 
		 } 
		 catch(Exception e) { 
      LOG.error("
          exception occurred while destroying ObjectFactory ["+objectFactory+"]", e); 
          } 
	 } 

	 //2 爲本線程銷燬 Dispatcher 實例
	 instance.set(null); 

	 //3 銷燬 DispatcherListeners(Dispatcher 監聽器 )。
	 if (!dispatcherListeners.isEmpty()) { 
		 for (DispatcherListener l : dispatcherListeners) { 
			 l.dispatcherDestroyed(this); 
		 } 
	 } 

	 //4 調用每一個攔截器的 destroy() 方法,銷燬每一個攔截器
	 Set<Interceptor> interceptors = new HashSet<Interceptor>(); 
	 Collection<Interceptor> packageConfigs = configurationManager. 
					 getConfiguration().getPackageConfigs().values(); 
	 for (PackageConfig packageConfig : packageConfigs) { 
		 for (Object config : packageConfig.getAllInterceptorConfigs().values()) {
          if (config instanceof InterceptorStackConfig) { 
                for (InterceptorMapping interceptorMapping : 
                   ((InterceptorStackConfig) config).getInterceptors()) { 
                     interceptors.add(interceptorMapping.getInterceptor()); 
				 } 
			 } 
		 } 
	 } 
	 for (Interceptor interceptor : interceptors) { 
		 interceptor.destroy(); 
	 } 

	 //5 銷燬 action context 
	 ActionContext.setContext(null); 

	 //6 銷燬 configuration 
	 configurationManager.destroyConfiguration(); 
	 configurationManager = null; 
 }

幾點說明:

  1. ObjectFactory 對象工廠,用來建立核心的框架對象,好比攔截器、Action、result 等等。本類處於 xwork 的包中。

  2. DispatcherListeners 爲監聽器設計模式,僅僅在 Dispatcher 的 init 和 destory 中執行,也就是說僅僅在 Dispatcher 初始化和銷燬的時候動做。

  3. actionContext 是一個 context,Action 在其中執行。actionContext 從根本上講是一個容器,action 執行所須要的對象,如會話,請求參數,區域信息,valueStack 等,包含在其中。

  4. 第 6 步銷燬 xwork 配置對象。

攔截器與 ActionContext

代理模式與切面

圖 4. 攔截器是一種代理設計模式的實現

圖片示例

代理模式有 3 個角色:

  1. 抽象主題 (abstract subject)

  2. 真實主題 (real subject)

  3. 代理主題 (proxy subject)

咱們以買筆記本電腦爲例 
抽象主題爲抽象類或接口,定義了 request() 的行爲,就是買電腦。
真實主題爲買 hp 筆記本,要調用實現接口的 request() 方法,固然你找不到 hp 公司,你只能找到銷售 hp 筆記本的電腦公司。
代理主題爲銷售 hp 筆記本的電腦公司。這家公司可能會說,今天買電腦都送一臺數碼相機,也可能跟你打折等等。總之在代理主題角色執行的時候,銷售公司能夠發生某些行爲,發生的這些行爲叫加強 advice,加強只能發生在代理角色。
代理模式的使用場景,加強是代理的目的。

清單 9. 代理模式
 public interface Subject { 
	 abstract public void request(); 
 } 

 class RealSubject implements Subject { 
	 public RealSubject() {} 

	 public void request() { 
		 System.out.println(" From real subject. "); 
	 } 
 } 

 // 代理角色
 class ProxySubject implements Subject { 
	 private RealSubject realSubject; // 真實主題對象

	 public ProxySubject() {} 
	 public void preRequest() {} 
	 public void postRequest() {} 

	 public void request() { 
		 preRequest(); 
		 if (realSubject == null) { 
			 realSubject = new RealSubject(); 
		 } 
		 // 此處執行真實對象的 request 方法
		 realSubject.request(); 
		 postRequest(); 
	 } 
 }

代理角色是切面,preRequest 爲前置加強,postRequest 爲後置加強。固然切面 aspect 的標準定義爲兩個要素:加強加切入。

圖 5. 假設你是這麼調用

圖片示例

你編寫的 preRequest() 和 postRequest() 方法必定會參與到真實主題的的 request() 方法執行中。
假設你還不瞭解,我想請問,若是有個機會,一個很漂亮的妹妹的 MM 要你幫她買東西,你會不會本身貼點錢,或者說些話,讓 MM 以爲開心一些。若是是,你就是切面,你的額外的事情和錢就是切面上的加強。

動態代理中的 代理角色 = 切面 = 攔截器。請看下面的實現。

清單 10. 動態代理的實現
 // 省略 Subject 接口和 RealSubject 類
 // 調用處理器的類
 class DynamicSubject implements InvocationHandler { 
	 private Object sub; 

	 public DynamicSubject() {} 

	 public DynamicSubject(Object obj) { 
		 sub = obj; 
	 } 

	 public Object invoke(Object proxy, Method method, Object[] args) 
			 throws Throwable { 
		 System.out.println(" before calling  " + method); 
		 method.invoke(sub, args); 
		 System.out.println(" after calling  " + method); 
		 return null; 
	 } 
 } 

 // 客戶類
 class Client { 
	 static public void main(String[] args) throws Throwable { 
		 RealSubject rs = new RealSubject(); // 真實主題
		 InvocationHandler ds = new DynamicSubject(rs); 
		 Class cls = rs.getClass(); 
		 // 生成代理對象
		 Subject subject = (Subject) Proxy.newProxyInstance( 
				 cls.getClassLoader(), cls.getInterfaces(), ds); 
		 subject.request(); 
	 } 
 }

動態代理必須依賴於反射。動態代理,代理類和代理對象都是運行時生成的 (runtime),因此稱爲動態代理。InvocationHandler 實現類的原代碼參與到代理角色的執行。通常在 Invoke 方法中實現加強。

好,在本部分總結的末尾,我再強調一遍概念:動態代理中的代理角色 = 切面 = 攔截器。

  1. Dispatcher 類的 serviceAction() 方法中,執行處理的核心語句爲 proxy.execute()。

  2. ActionProxy 類,也就是代理角色,持有 ActionInvocation 的實例引用。ActionInvocation 表明着 Action 執行的狀態,它持有着攔截器和 Action 實例的引用。ActionInvocation 經過反覆調用 invoke() 方法,調用沿着攔截器鏈向下走。走完攔截器鏈後運行 Action 實例,最後運行 Result。

  3. Result 每每對應網頁 (jsp,freemarker,velocity),網頁的結果被寫到輸出流裏。客戶端接收到請求的網頁。

  4. 你看到 postRequest() 方法了嗎,它在真實主題結束後運行。這也決定了,爲何 Action 運行結束後,控制權還要按照攔截器鏈返回。

圖 6. 本部分的總結

圖片示例

回頁首

關於 valueStack 的討論

前面咱們提到一個概念,value Stack 包含兩個部分。可是書上也說,不少時候或者是一般特指 Object Stack,用術語說就是 OGNL value stack。怎麼理解 ?

清單 11. 官方的表示
 |--application 
			 | 
			 |--session 
	 context map---| 
			 |--value stack(root) 
			 | 
			 |--request 
			 | 
			 |--parameters 
			 | 
			 |--attr (searches page, request,session, then application scopes)

說個人結論,而後再看原代碼。
Struts2 框架,把 ActionContext 設置爲 OGNL 上下文。ActionContext 持有 application,session,request,parameters 的引用。ActionContext 也持有 value stack 對象的引用 ( 注意這個時候 value stack 特指 Object stack)。
上述對象的引用,ActionContext 不直接持有,而是經過本身的屬性 Map<String, Object> context 持有引用。處理 OGNL 表達式最頂層的對象是 Map<String, Object> context。

清單 12. ActionContext 類
 public static final String ACTION_NAME = "com.opensymphony.xwork2.ActionContext.name"; 
    /** 
     * 值棧在 map 的 key,map 確定是 key-value 對結構了,別說你不知道。map 指本類最後一個屬性 context。
     */ 
    public static final String VALUE_STACK = ValueStack.VALUE_STACK; 

    /** 
     * session 的 key,如下省略
     */ 
    public static final String SESSION = 
    "com.opensymphony.xwork2.ActionContext.session"; 
    public static final String APPLICATION = 
    "com.opensymphony.xwork2.ActionContext.application"; 
	 public static final String PARAMETERS = 
     "com.opensymphony.xwork2.ActionContext.parameters"; 
    public static final String ACTION_INVOCATION = 
    "com.opensymphony.xwork2.ActionContext.actionInvocation"; 
    
 //map 的定義
    Map<String, Object> context; 

    public ActionInvocation getActionInvocation() { 
        return (ActionInvocation) get(ACTION_INVOCATION); 
    } 

	 // 對做用域對象的引用
    public Map<String, Object> getApplication() { 
        return (Map<String, Object>) get(APPLICATION); 
    } 
    public void setSession(Map<String, Object> session) { 
        put(SESSION, session); 
    } 
    public Map<String, Object> getSession() { 
        return (Map<String, Object>) get(SESSION); 
    } 
	 // 對 valueStack 的引用
    public void setValueStack(ValueStack stack) { 
        put(VALUE_STACK, stack); 
    } 
    public ValueStack getValueStack() { 
        return (ValueStack) get(VALUE_STACK); 
    } 
   	 // 最關鍵的代碼
    public Object get(String key) { 
        return context.get(key); 
    } 
    public void put(String key, Object value) { 
        context.put(key, value); 
    } 
 }

那麼 value stack 類是什麼樣子呢?值棧是一個數據結構的棧。全部的數據都保存在 root 對象中。

清單 13. ValueStack 類
 public interface ValueStack { 
    public abstract CompoundRoot getRoot(); 
	 // 省略細節
    public abstract Object peek(); 
    public abstract Object pop(); 
    public abstract void push(Object o); 
 } 

 public class CompoundRoot extends ArrayList { 
	 // 省略細節
 }

你所編寫的 Action 類實例,被放在 value stack 裏。OGNL 訪問 Action 實例的屬性,能夠省略 #。若是使用了 #, 表示所查找的對象不在 root 裏,而在其餘位置,好比 session。

在 Action 裏若是訪問 session ?最直接的方式是使用 ActionContext 獲得。第二種方式是實現 SessionAware 接口。

回頁首

個人總結和問題

我在總結以前仍是但願你們看一下官方的流程圖(圖 2)。

若是你能夠徹底看懂上面的圖,那你能夠省略這一部分。可是好在本部分都是精華的,並且很少。

  1. FilterDispatcher 接到請求,查找對應的 Action Mapping,調用 Dispatcher 類的 serviceAction() 方法。

  2. Dispatcher 類的 serviceAction() 方法中建立而且調用 ActionProxy。

  3. ActionProxy,持有 ActionInvocation 的實例引用。ActionInvocation 表明着 Action 執行的狀態,它持有着攔截器和 Action 實例的引用。ActionInvocation 經過反覆調用 invoke() 方法,調用沿着攔截器鏈向下走。

  4. 走完攔截器鏈後運行 Action 實例,最後運行 Result。

  5. 你們注意到攔截器鏈了嗎?它纔是 Struts2.0 的核心所在。

最後一個問題,一般咱們編寫 Struts2 只有一個過濾器 FilterDispatcher,爲何這邊是三個過濾器 ?SiteMesh 能夠對你編寫的頁面進行裝飾,以美化界面,固然筆者的界面剛好屬於通常般,剛脫離醜的那種類型。若是 SiteMesh 要訪問值棧 value stack,原來清除值棧的工做由 FilterDispatcher 完成。org.apache.struts2.dispatcher.ActionContextCleanUp 告訴 FilterDispatcher 不要清除值棧,由本身來清除。

相關文章
相關標籤/搜索