Struts2 源碼分析——調結者(Dispatcher)之action請求
章節簡言 |
上一章筆者講到關於struts2啟動的時候加載對應的準備工作。如加載配置文件struts.xml之類的信息。而相應的這些操作都離不開Dispatcher類的幫助。如果讀者只是認為Dispatcher類的作用只有這些。那真的是大錯特錯了。所以本章筆者將繼續講到關於Dispatcher類的另一個功能。即是StrutsPrepareFilter類倆項工作中的處理request請求相關信息。在講解之前,筆者還是想把相關的信息回想一下:當項目啟動的時候,strtus2也就啟動了。然後就會去初始化對應需要的信息(這部分內容上一章已經講到了)。之後當用戶在網址上輸入訪問的URL的時候。就會進入StrutsPrepareFilter類處理request請求的功能。好了。明白是什麽到這一步就可以了。因為下面就是要講到關於這一部分的內容。
調結者的action請求 |
StrutsPrepareFilter類在處理request請求的時候,需要用到一個叫PrepareOperations類的幫忙。PrepareOperations類可以說是StrutsPrepareFilter類和Dispatcher類的中間人。PrepareOperations類大部分的工作都是通過Dispatcher類完成的。先讓我們看一段代碼。如下
StrutsPrepareFilter類:
public void init(FilterConfig filterConfig) throws ServletException { InitOperations init = new InitOperations();//用於初始化相關的功能操作。你可以理解為工具類一樣子。 Dispatcher dispatcher = null;//這個類相當的重要。他的作用連接著StrutsExecuteFilter。這裏可以命名為調結者。 try { FilterHostConfig config = new FilterHostConfig(filterConfig);//這裏可以理解為把filterConfig在進行封裝FilterHostConfig更為主便操作和理解。 init.initLogging(config);//獲取名為loggerFactory的參數,並實例化這個類。一般為去用戶自定義日誌。 dispatcher = init.initDispatcher(config);//初化調結者。這裏是重要。 prepare = new PrepareOperations(dispatcher); this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);//加載排除在內的action的正則表達式 postInit(dispatcher, filterConfig); } finally { if (dispatcher != null) { dispatcher.cleanUpAfterInit(); } init.cleanup(); } }
從上面的紅色代碼我們可以看出來,PrepareOperations類在實例化時候,接受Dispatcher類作為構造函數的參數。即是在struts2啟動加載準備工作之後初始化。那麽PrepareOperations類到底又做哪些工作呢?讓我們在看一下下面的代碼。如下
StrutsPrepareFilter類:
1 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { 2 3 HttpServletRequest request = (HttpServletRequest) req; 4 HttpServletResponse response = (HttpServletResponse) res; 5 6 try { 7 if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { 8 request.setAttribute(REQUEST_EXCLUDED_FROM_ACTION_MAPPING, new Object()); 9 } else { 10 prepare.setEncodingAndLocale(request, response);//設置請求的格式編碼。 11 prepare.createActionContext(request, response);//action的上下文 12 prepare.assignDispatcherToThread();//把Dispatcher放入本地線程裏面。 13 request = prepare.wrapRequest(request); 14 prepare.findActionMapping(request, response);//找到action映射的信息 15 } 16 chain.doFilter(request, response); 17 } finally { 18 prepare.cleanupRequest(request); 19 } 20 }
上面代碼我們可以看出PrepareOperations類總共做了五件事情。先讓筆者簡單的講解一下:當用戶的request請求過來的時候,會判斷一下request請求是不是被排除之外的。如果是,則把REQUEST_EXCLUDED_FROM_ACTION_MAPPING常量作為KEY,object實例作為值存放在request的Attrbute裏面。這是為後面的StrutsExecuteFilter類作準備(下一章筆者會講到)。如果不是,則進行request請求處理。如下
1.設置request請求的本地化和格式編碼。實現上還是Dispatcher類在做工作。代碼如下
PrepareOperations類:
1 /** 2 * 設置本地化和請求格式編碼 3 * 4 * @param request servlet request 5 * @param response servlet response 6 */ 7 public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) { 8 dispatcher.prepare(request, response); 9 }
Dispatcher類:
1 /** 2 * 設置請求的本地化和格式編碼 3 * 4 * @param request 5 * The request 6 * @param response 7 * The response 8 */ 9 public void prepare(HttpServletRequest request, HttpServletResponse response) { 10 String encoding = null; 11 if (defaultEncoding != null) { 12 encoding = defaultEncoding; 13 } 14 15 if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) { 16 encoding = "UTF-8"; 17 } 18 19 Locale locale = null; 20 if (defaultLocale != null) { 21 locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale()); 22 } 23 24 if (encoding != null) { 25 applyEncoding(request, encoding); 26 } 27 28 if (locale != null) { 29 response.setLocale(locale); 30 } 31 32 if (paramsWorkaroundEnabled) { 33 request.getParameter("foo"); // simply read any parameter (existing 34 // or not) to "prime" the request 35 } 36 }
2.創建Action上下文(ActionContext類),並把上下文存放在本地線程(ThreadLocal)。所謂的上下文可以理解為把相同性質的業務歸為一類。而上下文就是這一類和外部相交處。所有的數據操作都可以通過他還完成。當然上下文的定義在不同的地方有不同的意思。請讀者自行找閱資料。先讓我們看一下代碼。如下
PrepareOperations類:
1 /** 2 * 創建Action的上下文 ,並存在到本地線程 3 * 4 * @param request servlet request 5 * @param response servlet response 6 * 7 * @return the action context 8 */ 9 public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) { 10 ActionContext ctx; 11 Integer counter = 1; 12 Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER); 13 if (oldCounter != null) { 14 counter = oldCounter + 1; 15 } 16 17 ActionContext oldContext = ActionContext.getContext(); 18 if (oldContext != null) { 19 // 有舊的action上下文,我們可以認為有可能是跳轉。 20 ctx = new ActionContext(new HashMap<>(oldContext.getContextMap())); 21 } else { 22 ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();//從容器中獲得值棧工廠,並新建值棧。 23 stack.getContext().putAll(dispatcher.createContextMap(request, response, null));//創建上下MAP並合到值棧裏面去。 24 ctx = new ActionContext(stack.getContext());//用值棧的上下MAP來新建上下文 25 } 26 request.setAttribute(CLEANUP_RECURSION_COUNTER, counter); 27 ActionContext.setContext(ctx); 28 return ctx; 29 }
從上面的紅色代碼我們就能夠看出第一次request請求就會創建一個新的action上下文(ActionContext)。同時也能明白ActionContext裏面存放大量的關於request請求對應的數據。而這些操作又離不開Dispatcher類的幫忙。最後把上下文(ActionContext類)存放到ActionContext類內部的本地線程(ThreadLocal)。代碼ActionContext.setContext(ctx)就是最好的說明。另外,上面有一點讓筆者一直不明白為什麽這麽做。覺得沒有什麽意義。即是獲得request的屬性為CLEANUP_RECURSION_COUNTER的值,然後進行計算操作的功能。筆者不得不將他理解為:用於計算request請求跳轉action的次數。就是一個request請求的生命周期通過了幾個action請求。如果不對的話,請讀者自行屏蔽。
3.把Dispatcher實例分配置到他內部的本地線程(ThreadLocal)。這一步主要是為了後面的StrutsExecuteFilter類的工作。讓我們看一下代碼吧。如下
PrepareOperations類:
1 /** 2 * dispatcher分配到Dispatcher類的本地線程 3 */ 4 public void assignDispatcherToThread() { 5 Dispatcher.setInstance(dispatcher); 6 }
4.把HttpServletRequest包裝為對應的StrutsRequestWrapper或是MultiPartRequestWrapper。主要是為了方便開發人員操作處理multipart而以。這裏比較簡單。筆者不想過的解釋。
5.找到對應action映射信息(ActionMapping類)。這部分的工作筆者認為是比較重要的。因為他將是StrutsExecuteFilter類工作的核心點。那麽ActionMapping類又是什麽呢?他是struts.xml配置文件上的action信息。有了他struts2才能知道當前請求是哪一個action類。那麽相關的知識筆者會在後面的章節講到。讓我們看一下代碼。如下
PrepareOperations類:
1 public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) { 2 ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY); 3 if (mapping == null || forceLookup) { 4 try { 5 mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager()); 6 if (mapping != null) { 7 request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping); 8 } 9 } catch (Exception ex) { 10 dispatcher.sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); 11 } 12 } 13 14 return mapping; 15 }
當筆者看到紅色代碼的時候,不得不說一句。看!又離不開Dispatcher類。同時值得註意是的ActionMapper類。所有的struts.xml配置文件的action信息都在這個類上面。即是可以通過ActionMapper類找到對應的action映射信息(ActionMapping)。從而找到對應的action類(用戶定義的action)。另外代碼request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping)這邊所做的事情。功能意思大家都能看得出來。那為什麽這麽做。主要還是因為後面的StrutsExecuteFilter類要用到。
從上面的五個事件中。筆者至少知道一點。Dispatcher類的工作真的很重要。而PrepareOperations類大部分只是一個中間人而以。當然這是筆者自己的理解。
Dispatcher的結束處理 |
筆者本來想把這個知識點做一個章節來講。可是想太少了。為什麽是結束工作呢?不管是struts2啟動的準備工作。還是啟動成功後的action請求工作。struts2都會在工作結束之進行一些處理。先看一下struts2啟動的準備工作成完之後的處理。如下
StrutsPrepareFilter類的init方法:
1 if (dispatcher != null) { 2 dispatcher.cleanUpAfterInit(); 3 } 4 init.cleanup();
Dispatcher類:
1 public void cleanUpAfterInit() { 2 if (LOG.isDebugEnabled()) { 3 LOG.debug("Cleaning up resources used to init Dispatcher"); 4 } 5 ContainerHolder.clear(); 6 }
InitOperations類:
1 public void cleanup() { 2 ActionContext.setContext(null); 3 }
從上面的代碼中我們可以看一個叫ContainerHolder類。這個類主要是用於存放Container容器。而上面在struts2啟動加載相關信息的準備工作結束之後。把Container容器給冊除了。同時也去掉了上下文(ActionContext類)。
讓我們看一下request請求結束後做了什麽。如下
StrutsPrepareFilter類的doFilter方法:
1 prepare.cleanupRequest(request);
PrepareOperations類:
1 public void cleanupRequest(HttpServletRequest request) { 2 Integer counterVal = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER); 3 if (counterVal != null) { 4 counterVal -= 1; 5 request.setAttribute(CLEANUP_RECURSION_COUNTER, counterVal); 6 if (counterVal > 0 ) { 7 LOG.debug("skipping cleanup counter={}", counterVal); 8 return; 9 } 10 } 11 // always clean up the thread request, even if an action hasn‘t been executed 12 try { 13 dispatcher.cleanUpRequest(request); 14 } finally { 15 ActionContext.setContext(null); 16 Dispatcher.setInstance(null); 17 devModeOverride.remove(); 18 } 19 }
Dispatcher類:
1 public void cleanUpRequest(HttpServletRequest request) { 2 ContainerHolder.clear(); 3 if (!(request instanceof MultiPartRequestWrapper)) { 4 return; 5 } 6 MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request; 7 multiWrapper.cleanUp(); 8 }
從上面的紅色代碼我們知道他在request請求結束之後,把Container容器給冊除了。本地線程的Dispatcher類的實例刪除了。上下文(ActionContext類)刪除了。
到了這裏筆者就明白了一點:
1.struts2啟動的時候,加載相關的配置信息。然後生成Dispatcher類的實例,初始化Container容器並把Dispatcher類的實例存放在PrepareOperations類裏面。那麽struts2的啟動準備工作結束,啟動成功。這時在把對應生成的Container容器和上下文(ActionContext類)刪除掉。
註意:上下文(ActionContext類)在這個時候可能是沒有生成的。但他做了刪除的工作。
2.啟動成功之後。用戶開始請求action。這個時候struts2會初始化一個新的Container容器和上下文(ActionContext類),分配Dispatcher類的實例到本地線程(ThreadLocal)中,找到對應的request請求的action映射(ActionMapping)並開始處理用戶對應的action請求。action請求成功之後,會刪除對應的Container容器、本地線程的Dispatcher類的實例、上下文(ActionContext類)。即是一個請求,一個Container容器,一個上下文(ActionContext類)。一個本地線程的Dispatcher類的實例。
註意:刪除Dispatcher類的實例是本地線程的。而不是PrepareOperations類的實例。(讀者不要搞錯了。然後一直會去想:刪除了Dispatcher類的實例。又在哪裏創建了。不好意思。啟動的時候就創建,之後就在也沒有了。)
本章總結 |
關於StrutsPrepareFilter類的工作。還有Dispatcher的作用。相信讀者看到這裏的時候,心裏都會有一個大概念的想法。當然筆者並非會專業的寫書者。所以可能有些讀者或多或少很難去理解筆者的意思。請見諒。關於調結者(Dispatcher)之倆章,主要就是想讓讀者明白Dispatcher做了什麽。和StrutsPrepareFilter類有什麽關系。為後面學習StrutsExecuteFilter類的工作做準備。
Struts2 源碼分析——調結者(Dispatcher)之action請求