北京,霧霾天氣阻止了今天的馬拉松之行,蝸居一天。爲一個問題“struts2如何保證ActionContext每次取的都是本次請求所對應的實例?”,給一個網友解釋了半天。
首先,我們知道,struts2和struts1的一個重要區別就是它進行了Action類和Servlet的解耦。而又提供了獲取Servlet API的其它通道,就是ActionContext(別跟我說還有個ServletActionContext,其實ServletActionContext只是ActionContext的一個子類而已)。源碼爲證:
public class ServletActionContext extends ActionContext implements StrutsStatics
其次,他也知道,ActionContext是Action執行時的上下文,可以看作是一個容器,並且這個容器只是一個Map而已,在容器中存放的是Action在執行時需要用到的VALUE_STACK、ACTION_NAME、SESSION、APPLICATION、ACTION_INVOCATION等等對象,還可以存放自定義的一些對象。我想用過struts2的朋友們,大多也都知道這些吧。
第三,他奇怪的是,在一個請求的處理過程攔截器、action類和result中任何時候獲取的ActionContext都是跟當前請求綁定那一個。爲什麼!?
我給他的建議是,帶着問題讀源碼,呵呵。那我們一起來看看吧:
首先是ActionContext類的源碼:
public class ActionContext implements Serializable{ static ThreadLocal actionContext = new ThreadLocal(); public static final String ACTION_NAME = "com.opensymphony.xwork2.ActionContext.name"; public static final String VALUE_STACK = "com.opensymphony.xwork2.util.ValueStack.ValueStack"; 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 LOCALE = "com.opensymphony.xwork2.ActionContext.locale"; public static final String TYPE_CONVERTER = "com.opensymphony.xwork2.ActionContext.typeConverter"; public static final String ACTION_INVOCATION = "com.opensymphony.xwork2.ActionContext.actionInvocation"; public static final String CONVERSION_ERRORS = "com.opensymphony.xwork2.ActionContext.conversionErrors"; public static final String CONTAINER = "com.opensymphony.xwork2.ActionContext.container"; Map<String, Object> context; public ActionContext(Map<String, Object> context) { this.context = context; } //... ... public static void setContext(ActionContext context) { actionContext.set(context); } public static ActionContext getContext() { return (ActionContext)actionContext.get(); } public void setContextMap(Map<String, Object> contextMap) { getContext().context = contextMap; } public Map<String, Object> getContextMap() { return this.context; } //... ... public void setSession(Map<String, Object> session) { put("com.opensymphony.xwork2.ActionContext.session", session); } public Map<String, Object> getSession() { return (Map)get("com.opensymphony.xwork2.ActionContext.session"); } //... ... public Object get(String key) { return this.context.get(key); } public void put(String key, Object value) { this.context.put(key, value); } }
源碼清晰的說明了我們編程中再熟悉不過的一行代碼:ActionContext ctx = ActionContext.getContext();,原來我們所取得的ctx來自於ThreadLocal啊!熟悉ThreadLocal的朋友都知道它是與當前線程綁定的,而且是我們Java中處理多線程問題的一種重要方式。我們再看,類中有個Map類型的變量context,其實,它纔是前面我們提到的真正意義上的“容器”,用來存放Action在執行時所需要的那些數據。
到這裏,他最初的那個問題已經很瞭然了。但是,他緊接着又一個疑惑提出來了:“那既然每個請求處理線程都有自己的ActionContext,那裏面的那些數據是什麼時候放進去的呢”?
這次我給他的建議是,動腦筋,用源碼驗證。既然ActionContext存放有HttpServletRequest及其中的參數,既然ActionContext貫穿於整個請求處理過程,那就從struts2請求處理的入口(過濾器StrutsPrepareAndExecuteFilter)找,源碼:
public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter { // ... ... public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)req; HttpServletResponse response = (HttpServletResponse)res; try { this.prepare.setEncodingAndLocale(request, response); this.prepare.createActionContext(request, response);//就是在這裏進行創建並初始化ActionContext實例 this.prepare.assignDispatcherToThread(); if ((this.excludedPatterns != null) && (this.prepare.isUrlExcluded(request, this.excludedPatterns))) { chain.doFilter(request, response); } else { request = this.prepare.wrapRequest(request); ActionMapping mapping = this.prepare.findActionMapping(request, response, true); if (mapping == null) { boolean handled = this.execute.executeStaticResourceRequest(request, response); if (!handled) chain.doFilter(request, response); } else { this.execute.executeAction(request, response, mapping); } } } finally { this.prepare.cleanupRequest(request); } } //... ... }
再找到prepare對應的類PrepareOperations,查看方法createActionContext(),就一目瞭然了。
對於ServletActionContext作爲ActionContext一個直接子類,原理也是類似的,感興趣的朋友可以看一下。
幫助別人,同時也是幫助自己。把這個處理的過程記錄下來,希望對需要的朋友有所幫助。