調(diào)結(jié)者的action請求 |
StrutsPrepareFilter類在處理request請求的時候,需要用到一個叫PrepareOperations類的幫忙。PrepareOperations類可以說是StrutsPrepareFilter類和Dispatcher類的中間人。PrepareOperations類大部分的工作都是通過Dispatcher類完成的。先讓我們看一段代碼。如下
StrutsPrepareFilter類:
public void init(FilterConfig filterConfig) throws ServletException { InitOperations init = new InitOperations();//用于初始化相關(guān)的功能操作。你可以理解為工具類一樣子。 Dispatcher dispatcher = null;//這個類相當(dāng)?shù)闹匾?。他的作用連接著StrutsExecuteFilter。這里可以命名為調(diào)結(jié)者。 try { FilterHostConfig config = new FilterHostConfig(filterConfig);//這里可以理解為把filterConfig在進(jìn)行封裝FilterHostConfig更為主便操作和理解。 init.initLogging(config);//獲取名為loggerFactory的參數(shù),并實例化這個類。一般為去用戶自定義日志。 dispatcher = init.initDispatcher(config);//初化調(diào)結(jié)者。這里是重要。 prepare = new PrepareOperations(dispatcher); this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);//加載排除在內(nèi)的action的正則表達(dá)式 postInit(dispatcher, filterConfig); } finally { if (dispatcher != null) { dispatcher.cleanUpAfterInit(); } init.cleanup(); } }
從上面的紅色代碼我們可以看出來,PrepareOperations類在實例化時候,接受Dispatcher類作為構(gòu)造函數(shù)的參數(shù)。即是在struts2啟動加載準(zhǔn)備工作之后初始化。那么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);//設(shè)置請求的格式編碼。 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類總共做了五件事情。先讓筆者簡單的講解一下:當(dāng)用戶的request請求過來的時候,會判斷一下request請求是不是被排除之外的。如果是,則把REQUEST_EXCLUDED_FROM_ACTION_MAPPING常量作為KEY,object實例作為值存放在request的Attrbute里面。這是為后面的StrutsExecuteFilter類作準(zhǔn)備(下一章筆者會講到)。如果不是,則進(jìn)行request請求處理。如下
1.設(shè)置request請求的本地化和格式編碼。實現(xiàn)上還是Dispatcher類在做工作。代碼如下
PrepareOperations類:
1 /**2 * 設(shè)置本地化和請求格式編碼3 *4 * @param request servlet request5 * @param response servlet response6 */7 public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {8 dispatcher.prepare(request, response);9 }
Dispatcher類:
1 /** 2 * 設(shè)置請求的本地化和格式編碼 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 (existing34 // or not) to "prime" the request35 }36 }
2.創(chuàng)建Action上下文(ActionContext類),并把上下文存放在本地線程(ThreadLocal)。所謂的上下文可以理解為把相同性質(zhì)的業(yè)務(wù)歸為一類。而上下文就是這一類和外部相交處。所有的數(shù)據(jù)操作都可以通過他還完成。當(dāng)然上下文的定義在不同的地方有不同的意思。請讀者自行找閱資料。先讓我們看一下代碼。如下
PrepareOperations類:
1 /** 2 * 創(chuàng)建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上下文,我們可以認(rèn)為有可能是跳轉(zhuǎn)。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));//創(chuàng)建上下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請求就會創(chuàng)建一個新的action上下文(ActionContext)。同時也能明白ActionContext里面存放大量的關(guān)于request請求對應(yīng)的數(shù)據(jù)。而這些操作又離不開Dispatcher類的幫忙。最后把上下文(ActionContext類)存放到ActionContext類內(nèi)部的本地線程(ThreadLocal)。代碼ActionContext.setContext(ctx)就是最好的說明。另外,上面有一點讓筆者一直不明白為什么這么做。覺得沒有什么意義。即是獲得request的屬性為CLEANUP_RECURSION_COUNTER的值,然后進(jìn)行計算操作的功能。筆者不得不將他理解為:用于計算request請求跳轉(zhuǎn)action的次數(shù)。就是一個request請求的生命周期通過了幾個action請求。如果不對的話,請讀者自行屏蔽。
3.把Dispatcher實例分配置到他內(nèi)部的本地線程(ThreadLocal)。這一步主要是為了后面的StrutsExecuteFilter類的工作。讓我們看一下代碼吧。如下
PrepareOperations類:
1 /**2 * dispatcher分配到Dispatcher類的本地線程3 */4 public void assignDispatcherToThread() {5 Dispatcher.setInstance(dispatcher);6 }
4.把HttpServletRequest包裝為對應(yīng)的StrutsRequestWrapper或是MultiPartRequestWrapper。主要是為了方便開發(fā)人員操作處理multipart而以。這里比較簡單。筆者不想過的解釋。
5.找到對應(yīng)action映射信息(ActionMapping類)。這部分的工作筆者認(rèn)為是比較重要的。因為他將是StrutsExecuteFilter類工作的核心點。那么ActionMapping類又是什么呢?他是struts.xml配置文件上的action信息。有了他struts2才能知道當(dāng)前請求是哪一個action類。那么相關(guān)的知識筆者會在后面的章節(jié)講到。讓我們看一下代碼。如下
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 }
當(dāng)筆者看到紅色代碼的時候,不得不說一句???!又離不開Dispatcher類。同時值得注意是的ActionMapper類。所有的struts.xml配置文件的action信息都在這個類上面。即是可以通過ActionMapper類找到對應(yīng)的action映射信息(ActionMapping)。從而找到對應(yīng)的action類(用戶定義的action)。另外代碼request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping)這邊所做的事情。功能意思大家都能看得出來。那為什么這么做。主要還是因為后面的StrutsExecuteFilter類要用到。
從上面的五個事件中。筆者至少知道一點。Dispatcher類的工作真的很重要。而PrepareOperations類大部分只是一個中間人而以。當(dāng)然這是筆者自己的理解。
Dispatcher的結(jié)束處理 |
筆者本來想把這個知識點做一個章節(jié)來講??墒窍胩倭恕槭裁词墙Y(jié)束工作呢?不管是struts2啟動的準(zhǔn)備工作。還是啟動成功后的action請求工作。struts2都會在工作結(jié)束之進(jìn)行一些處理。先看一下struts2啟動的準(zhǔn)備工作成完之后的處理。如下
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啟動加載相關(guān)信息的準(zhǔn)備工作結(jié)束之后。把Container容器給冊除了。同時也去掉了上下文(ActionContext類)。
讓我們看一下request請求結(jié)束后做了什么。如下
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 executed12 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請求結(jié)束之后,把Container容器給冊除了。本地線程的Dispatcher類的實例刪除了。上下文(ActionContext類)刪除了。
到了這里筆者就明白了一點: