北京,雾霾天气阻止了今天的马拉松之行,蜗居一天。为一个问题“struts2如何保证ActionContext每次取的都是本次请求所对应的实例?”,给一个网友解释了半天。

   首先,我们知道,struts2struts1的一个重要区别就是它进行了Action类和Servlet的解耦。而又提供了获取Servlet API的其它通道,就是ActionContext(别跟我说还有个ServletActionContext,其实ServletActionContext只是ActionContext的一个子类而已)。源码为证:

public class ServletActionContext extends ActionContext implements StrutsStatics

   其次,他也知道,ActionContextAction执行时的上下文,可以看作是一个容器,并且这个容器只是一个Map而已,在容器中存放的是Action在执行时需要用到的VALUE_STACKACTION_NAMESESSIONAPPLICATIONACTION_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
 context;  public ActionContext(Map
 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
 contextMap)  {    getContext().context = contextMap;  }  public Map
 getContextMap()  {    return this.context;  }  //... ...  public void setSession(Map
 session)  {    put("com.opensymphony.xwork2.ActionContext.session", session);  }  public Map
 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一个直接子类,原理也是类似的,感兴趣的朋友可以看一下。

   帮助别人,同时也是帮助自己。把这个处理的过程记录下来,希望对需要的朋友有所帮助。