struts2处理请求流程详解
struts2大概分為兩塊:一是struts2系統初始化,二是struts2處理請求,對請求作出響應。
下面就說說個人對struts2對請求處理流程的理解:
下面是StrutsPrepareAndExecuteFilter過濾器的doFilter方法中的主要代碼:
prepare.setEncodingAndLocale(request, response); prepare.createActionContext(request, response); prepare.assignDispatcherToThread(); if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {chain.doFilter(request, response);} else {request = prepare.wrapRequest(request);ActionMapping mapping = prepare.findActionMapping(request, response, true);if (mapping == null) {boolean handled = execute.executeStaticResourceRequest(request, response);if (!handled) {chain.doFilter(request, response);}} else {execute.executeAction(request, response, mapping);}}?
在系統初始化的時候StrutsPrepareAndExecuteFilter的init方法被執行,實例化出了PrepareOperations和ExecuteOperations兩個對象,第一個對象是對真正響應請求之前所作的一些準備操作封裝,ExecuteOperations是對響應請求所作的封裝,但其實這兩個對象最終調用的都是核心分發器Dispatcher對象的方法。
prepare.setEncodingAndLocale(request, response);這一句處理請求編碼與響應Locale,其內部調用的就是Dipatcher的prepare方法,下面是源碼:
?
/*** Sets the request encoding and locale on the response*/public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {dispatcher.prepare(request, response);}其邏輯是如果在在struts2的配置文件中指寫了i18n編碼則使用配置文件中的編碼,否則不會調用request.setCharacterEncoding()方法。至于響應Locale的取值與encoding的原理是一樣的,但具體的邏輯源碼有點多,但不難,這里就不作解釋了,大家應該都看得懂。
?
prepare.createActionContext(request, response);
ActionContext oldContext = ActionContext.getContext();if (oldContext != null) {// detected existing context, so we are probably in a forward //因為項目一般都不會是分布式應用,也就不會執行這里ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));} else { //這里是會執行的代碼ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));ctx = new ActionContext(stack.getContext());}request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);ActionContext.setContext(ctx);return ctx;ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();從struts2容器中獲取ValueStackFactory并創建出ValueStack
?
stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));把ActionContext中的數據復制一份到ValueStack中,所以從ActionContext與ValueStack都能拿到我們想要的所有數據。
dispatcher.createContextMap
?
// request map wrapping the http request objectsMap requestMap = new RequestMap(request);// parameters map wrapping the http parameters. ActionMapping parameters are now handled and applied separatelyMap params = new HashMap(request.getParameterMap());// session map wrapping the http sessionMap session = new SessionMap(request);// application map wrapping the ServletContextMap application = new ApplicationMap(context);Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);if (mapping != null) {extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);}return extraContext;
該方法中把Servlet原生的Request,Parameters,Session,ServletContext都轉換成了Map,然后將轉換后的Map與原生的Servlet對象都傳進了Dispatcher另一個重載的createContextMap方法,下面是其源碼:
?
?
public HashMap<String,Object> createContextMap(Map requestMap,Map parameterMap,Map sessionMap,Map applicationMap,HttpServletRequest request,HttpServletResponse response,ServletContext servletContext) {HashMap<String,Object> extraContext = new HashMap<String,Object>();extraContext.put(ActionContext.PARAMETERS, new HashMap(parameterMap));extraContext.put(ActionContext.SESSION, sessionMap);extraContext.put(ActionContext.APPLICATION, applicationMap);Locale locale;if (defaultLocale != null) {locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());} else {locale = request.getLocale();}extraContext.put(ActionContext.LOCALE, locale);//extraContext.put(ActionContext.DEV_MODE, Boolean.valueOf(devMode));extraContext.put(StrutsStatics.HTTP_REQUEST, request);extraContext.put(StrutsStatics.HTTP_RESPONSE, response);extraContext.put(StrutsStatics.SERVLET_CONTEXT, servletContext);// helpers to get access to request/session/application scopeextraContext.put("request", requestMap);extraContext.put("session", sessionMap);extraContext.put("application", applicationMap);extraContext.put("parameters", parameterMap);AttributeMap attrMap = new AttributeMap(extraContext);extraContext.put("attr", attrMap);return extraContext;}
該方法主要就是把轉換后的Map與Servlet原生對象外加一個AttributeMap都存進了一個大Map中,然后返回。
?
這樣就即能獲取轉換后的Map,又可以獲取Servlet原生對象。
現在回到prepare.createActionContext方法中
ActionContext.setContext(ctx);把創建的ActionContext對象放進ThreadLocal<T>中,綁定到當前線程以在其它地方方便獲取ActionContext對象。
再加到核心過濾器中,prepare.assignDispatcherToThread();從該方法的名稱就知道是把Dispatcher對象綁定到當前線程,也就是放進了一個ThreadLocal<T>對象中,這樣做的目的是為了解決多線程并發訪問的問題,因為Dispathcer對象只創建了一個,創建代碼就在StrutsPrepareAndExecuteFilter的init方法當中,而init方法只會執行一次,當然Dispatcher對象也就只有一個了,而Web應用天生就是一個多線程的環境,所以把Dispatcher放進ThreadLocal<T>中成為了最佳選擇。這里與上面ActionContext對象放進ThreadLocal<T>中的原因是不一樣的,因為每當一個請求到來系統都會為其創建一個ActionContext對象,這個ActionContext是該請求獨享的,并不存在多線程的問題,所以把該對象放進ThreadLocal<T>中是為了獲取方便。
if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
}該判斷是因為struts2支持不包含的URL模式匹配,一個請求雖然進入了struts2過濾器,但如果該請求的URL在不包含之列的話sturts2只是單純地調用chain.doFilter方法放行。其它情況就會進入else部分,調用Action處理請求。
request = prepare.wrapRequest(request);對HttpServletRequest進行包裝,參看其源碼就可以知道如果是文件上傳把HttpServletRequest包裝成了一個MultiPartRequestWrapper對象,該對象專門處理文件上傳請求。如果不是文件上傳進把HttpServletRequest包裝成了StrutsRequestWrapper,StrutsRequestWrapper類覆蓋了其父類的getAttribute方法,對該方法的行為作了一點修改,其父類即javax.servlet.http.HttpServletRequestWrapper中的getAttribute方法只是從request對象查找指定屬性,而StrutsRequestWrapper的getAttribute方法是先在request對象中進行查找,如果沒有找到還會去ValueStack中進行查找,下面的源碼即是證明:
?
// If not found, then try the ValueStackctx.put("__requestWrapper.getAttribute", Boolean.TRUE);ValueStack stack = ctx.getValueStack();if (stack != null) {attribute = stack.findValue(s);}這就是為什么在JSP頁面中用EL表達式也能訪問到ValueStack中值的屬性的原因。
?
ActionMapping mapping = prepare.findActionMapping(request, response, true);這句就沒什么說了,就是得到Action映射信息。
如果請求的靜態頁面就會執行execute.executeStaticResourceRequest(request, response);在方法內部會判斷有無能力對該請求進行處理,如果有則處理沒有則調用chain.doFilter放行,實現基本都一樣就是原樣返回給客戶端了。
真正會執行Action的是這一句:execute.executeAction(request, response, mapping);該方法調用的是Dispatcher的serviceAction方法,我們進入該方法看看,下面是該方法中的重要代碼:
?
Configuration config = configurationManager.getConfiguration();ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, method, extraContext, true, false);request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());// if the ActionMapping says to go straight to a result, do it!if (mapping.getResult() != null) {Result result = mapping.getResult();result.execute(proxy.getInvocation());} else {proxy.execute();}// If there was a previous value stack then set it back onto the requestif (!nullStack) {request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);}
先獲取Configuration對象,通過Configuration得到容器對象,再從容器中獲取ActionProxyFactory,ActionProxy工廠類,然后創建ActionProxy,大家都知道struts2內部包裝的是webwork框架,而ActionProxy正是sturts與webwork的分界線,ActionProxy是進行webwork框架的門戶。
?
struts2默認使用的是DefaultActionProxyFactory創建ActionProxy對象的,下面是其createActionFactor方法:
?
public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {ActionInvocation inv = new DefaultActionInvocation(extraContext, true);container.inject(inv);return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);} public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {DefaultActionProxy proxy = new DefaultActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);container.inject(proxy);proxy.prepare();return proxy;}這是兩個重載的方法,第一個方法調用了第二個方法,在第一個方法中創建了ActionInvocation對象,并對其依賴的對象使用容器進行注入,緊接著就創建了ActionProxy對象,也對其依賴對象進行了注入,如果ObjectFctory,Configuration對象等。然后高用proxy.prepare()方法,其中有一個resolveMethod();的方法,該方法很簡單,就是如果在配置Action的時候沒有指定method屬性的時候會把method屬性值賦為execute,這就是為什么execute是Action默認的執行方法的原因。還有一個很重要的方法叫invocation.init(this);即調用ActionInvocation的init方法,把ActionProxy自己傳了進行,當然ActionInvocation中會對ActionProxy進行緩存。
?
struts2對ActionInvocation的默認實現是DefaultActionInvocation類,進放該類的init方法,下面是該方法中重要的代碼:
?
createAction(contextMap);if (pushAction) {stack.push(action);contextMap.put("action", action);}invocationContext = new ActionContext(contextMap);invocationContext.setName(proxy.getActionName());// get a new List so we don't get problems with the iterator if someone changes the listList<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());interceptors = interceptorList.iterator();第一句即創建Action,如果大家對struts2的對象創建有所了解的話就知道,Action,Result,Intercepter的創建都是由ObjectFactory的buildBean方法來創建的,其內部就是調用Class.newInstance();創建的,所以Action一定要有一個無參構造方法。
?
注意這句:stack.push(action);這里把創建的Action壓進了ValuesStack中,這就是為什么默認Action在棧頂的原因。下面就是獲取該Action配置的所有攔截器并進行緩存在ActionInvocation中,Action也是如此,因為Action與Interceptor的執行調度就是由ActionInvocation實現的。
現在回到Dispatcher的serviceAction方法中,創建出來ActionProxy對象后的下一句代碼把ValueStack對象放進了request中,這也就意味著我們通過HttpServletRequest對象也是可獲取,只要知道key就行了。
proxy.execute();這一句執行ActionProxy的execute方法,struts2中ActionProxy的默認實現是StrutsActionProxy,下面進行該類的execute方法,源碼如下:
?
public String execute() throws Exception {ActionContext previous = ActionContext.getContext();ActionContext.setContext(invocation.getInvocationContext());try { // This is for the new API: // return RequestContextImpl.callInContext(invocation, new Callable<String>() { // public String call() throws Exception { // return invocation.invoke(); // } // });return invocation.invoke();} finally {if (cleanupContext)ActionContext.setContext(previous);}}代碼很簡單,就是調用ActionInvocation的invoke方法,執行攔截器與Action,下面是invoke方法的源碼,因該方法中附屬代碼較多,這里只撿出了重要代碼:
?
?
if (interceptors.hasNext()) {final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();String interceptorMsg = "interceptor: " + interceptor.getName();UtilTimerStack.push(interceptorMsg);try {resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);}finally {UtilTimerStack.pop(interceptorMsg);}} else {resultCode = invokeActionOnly();} //這里省略了一些代碼... // now execute the result, if we're supposed toif (proxy.getExecuteResult()) {executeResult();}顯示這里就是在執行攔截器棧中的所有攔截器,當攔截器執行完后就會根據配置執行Action中相應的方法,執行完后得到了一個resultCode字符串,系統就是根據這個字符串去查找相應的Result。
?
緊湊著就是執行executeResult方法,該方法中就是根據Result配置創建出相應的Result對象,然后執行Result的execute方法,例如用得最多的ServletDispatcherResult,其execute方法主要就是調用dispatcher.forward(request, response)方法,返回一個頁面給Tomcat進行解析,然后將解析后的內容呈現給客戶端瀏覽器。
至此,struts2的整個執行流程基本上就講完了,如果有錯誤之處,盡請指正。
下面上傳的是個人為struts2執行流程畫的一個時序圖,有興趣的可以看看,因圖片太大所以要放大了才看得清,希望有所幫助:
?
轉載于:https://www.cnblogs.com/pangblog/p/3357891.html
總結
以上是生活随笔為你收集整理的struts2处理请求流程详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 细说PHP中strlen和mb_strl
- 下一篇: 在nginx下配置PATH_INFO的方