ActionContext 、Interceptor和ActionInvocation



Interceptor

拦截器参考http://www.doc88.com/p-742828789802.html,写的很好很详细

2、拦截器与过滤器的区别 :
1. 拦截器是基于java的
反射机制的,而过滤器是基于函数回调
2. 拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
3. 拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
4. 拦截器可以访问action
上下文、值栈里的对象,而过滤器不能访问。
5. 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器
初始化时被调用一次



首先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 () ,就一目了然了。



interceptor说明

Interceptor的接口定义没有什么特别的地方,除了init和destory方法以外,intercept方法是实现整个拦截器机制的核心方法。而它所依赖的参数ActionInvocation则是我们之前章节中曾经提到过的著名的Action调度者

我在这里需要指出的是一个很重要的方法invocation.invoke()。这是ActionInvocation中的方法,而ActionInvocation是Action调度者,所以这个方法具备以下2层含义(详细看DefaultActionInvocation源代码): 
1. 如果拦截器堆栈中还有其他的Interceptor,那么invocation.invoke()将调用堆栈中下一个Interceptor的执行。 

2. 如果拦截器堆栈中只有Action了,那么invocation.invoke()将调用Action执行。

 

3.

ActionContext 、Interceptor和ActionInvocation_第1张图片

DefaultActionInvocation部分源代码:

      if (interceptors.hasNext()) {
       final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
       UtilTimerStack.profile("interceptor: "+interceptor.getName(), 
         new UtilTimerStack.ProfilingBlock() {
       public String doProfiling() throws Exception {
           resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);//递归调用拦截器
           return null;
       }
       });
      } else {
       resultCode = invokeActionOnly();
      }

每个拦截器中的代码的执行顺序,在Action之前,拦截器的执行顺序与堆栈中定义的一致;而在Action和Result之后,拦截器的执行顺序与堆栈中定义的顺序相反。

Interceptor拦截类型 

从上面的分析,我们知道,整个拦截器的核心部分是invocation.invoke()这个函数的调用位置。事实上,我们也正式根据这句代码的调用位置,来进行拦截类型的区分的。在Struts2中,Interceptor的拦截类型,分成以下三类: 

1. before 

before拦截,是指在拦截器中定义的代码,它们存在于invocation.invoke()代码执行之前。这些代码,将依照拦截器定义的顺序,顺序执行。 

2. after 

after拦截,是指在拦截器中定义的代码,它们存在于invocation.invoke()代码执行之后。这些代码,将一招拦截器定义的顺序,逆序执行

 

PreResultListener 
有的时候,before拦截和after拦截对我们来说是不够的,因为我们需要在Action执行完之后,但是还没有回到视图层之前,做一些事情。Struts2同样支持这样的拦截,这种拦截方式,是通过在拦截器中注册一个PreResultListener的接口来实现的。

如:在拦截器中使用如下代码,其中MyPreResultListener实现了PreResultListener 接口并在beforeResult方法中做了一些事情然后在拦截器类中加入action.addPreResultListener(new MyPreResultListener());

 

从源码中,我们可以看到,我们之前提到的Struts2的Action层的4个不同的层次,在这个方法中都有体现,他们分别是:拦截器(Interceptor)、Action、PreResultListener和Result。在这个方法中,保证了这些层次的有序调用和执行

 

问题

使用Struts2作为web框架,知道它的拦截器(Interceptor)机制,类似与Filter和Spring的AOP,于是实现了一个为Action增加自定义前置(before)动作和后置动作(after)的拦截器(曰:WInterceptor),不过用一段时间发现,在WInterceptor的after中,对Action对象的属性修改在页面看不到,对请求对象的属性设置也无效。为什么在调用了Action之后(invokeAction())之后,request就不能使用了呢,拦截器不能改变Action的Result么?

 问题的关键在于,在调用actionInvocation.invoke()的之后,不仅执行类Action,也执行类Result。因而,等退回到拦截器的调用代码时,Result已经生成,View已经确定,这时你再修改模型(Action的属性)或请求对象的属性,对视图不会有任何影响。

解决办法:

 

 方法一:使用现成的PreResultListener监听器事件

 

 

搞清楚原因,卷起袖子干吧,只要让WInterpretor的after事件,放在Result的生成之前就行了。

看看XWork的拦截器接口注入的actionInvocation,其实就提供增加Result执行的前置监听事件-PreResultListener:




你可能感兴趣的:(ActionContext 、Interceptor和ActionInvocation)