zoukankan      html  css  js  c++  java
  • struts2拦截器的实现原理及源码剖析

    拦截器(interceptor)是Struts2最强大的特性之一,也可以说是struts2的核心,拦截器可以让你在Action和result被执行之前或之后进行一些处理。同时,拦截器也可以让你将通用的代码模块化并作为可重用的类。Struts2中的很多特性都是由拦截器来完成的。拦截是AOP(Aspect Objected Programing:面向切面编程)的一种实现策略。在Webwork的中文文档的解释为:拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行同时也是提供了一种可以提取action中可重用的部分的方式。谈到拦截器,还有一个词大家应该知道——拦截器链(Interceptor Chain,在Struts 2中称为拦截器栈Interceptor Stack)。拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。

    一.拦截器的实现原理:

      大部分时候,拦截器方法都是通过代理的方式来调用的。Struts 2的拦截器实现相对简单。当请求到达Struts 2的ServletDispatcher时,Struts 2会查找配置文件,并根据其配置实例化相对的拦截器对象,然后串成一个列表(list),最后一个一个地调用列表中的拦截器。事实上,我们之所以能够如此灵活地使用拦截器,完全归功于“动态代理”的使用。动 态代理是代理对象根据客户的需求做出不同的处理。对于客户来说,只要知道一个代理对象就行了。那Struts2中,拦截器是如何通过动态代理被调用的呢? 当Action请求到来的时候,会由系统的代理生成一个Action的代理对象,由这个代理对象调用Action的execute()或指定的方法,并在 struts.xml中查找与该Action对应的拦截器。如果有对应的拦截器,就在Action的方法执行前(后)调用这些拦截器;如果没有对应的拦截 器则执行Action的方法。其中系统对于拦截器的调用,是通过ActionInvocation来实现的。代码如下:
    if (interceptors.hasNext()) {
    Interceptor interceptor=(Interceptor)interceptors.next();
    resultCode = interceptor.intercept(this);
    } else {
    if (proxy.getConfig().getMethodName() == null) {
    resultCode = getAction().execute();
    } else {
    resultCode = invokeAction(getAction(), proxy.getConfig());
    }
    }

    可以发现Action并没有与拦截器发生直接关联,而完全是代理在组织Action与拦截器协同工作。如下图:struts2拦截器的实现原理及源码剖析    我们只知道拦截器调用栈的最底层,是Action方法的调用,却不知道Result的调用也是在栈底调用,之后才返回给上一个拦截器,层层退出
     

         Struts2的拦截器结构的设计,实际上是一个典型的责任链模式的应用。首先将整个执行划分成若干相同类型的元素,每个元素具备不同的逻辑责任,并将他们纳入到一个链式的数据结构中(我们可以把堆栈结构也看作是一个递归的链式结构),而每个元素又有责任负责链式结构中下一个元素的执行调用。

    这样的设计,从代码重构的角度来看,实际上是将一个复杂的系统,分而治之,从而使得每个部分的逻辑能够高度重用并具备高度可扩展性。所以,Interceptor结构实在是Struts2/Xwork设计中的精华之笔。

    二.拦截器执行分析

         我们大家都知道,Interceptor的接口定义没有什么特别的地方,除了init和destory方法以外,intercept方法是实现整个拦截器 机制的核心方法。而它所依赖的参数ActionInvocation则是著名的Action调度者。我们再来看看一个典型的Interceptor的抽象 实现类:
    public abstract class AroundInterceptor extends AbstractInterceptor {
    //com.opensymphony.xwork2.interceptor.AbstractInterceptor#int//ercept(com.opensymphony.xwork2.ActionInvocation)
    @Override

    public String intercept(ActionInvocation invocation) throws Exception {
    String result = null;
        before(invocation);
    // 调用下一个拦截器,如果拦截器不存在,则执行Action

            result = invocation.invoke();

            after(invocation, result);


            return result;
    }


    public abstract void before(ActionInvocation invocation) throws Exception;
    public abstract void after(ActionInvocation invocation, String resultCode) throws Exception;
    }
     在这个实现类中,实际上已经实现了最简单的拦截器的雏形。这里需要指出的是一个很重要的方法invocation.invoke()。这是 ActionInvocation中的方法,而ActionInvocation是Action调度者,所以这个方法具备以下4层含义:

    1. 如果拦截器堆栈中还有其他的Interceptor,那么invocation.invoke()将调用堆栈中下一个Interceptor的执行。
    2. 如果拦截器堆栈中只有Action了,那么invocation.invoke()将调用Action执行。

    3、如果最后一个拦截器中intercept()方法没有调用invocation.invoke()方法,那么将根据intercept()方法返回的字符串,去Result中寻找相应的视图.  (其实如果拦截没有调用invocation.invoke方法,毋庸置疑,他就是最后一个拦截器中(共两种)的一种了!!!!
    4.由责任链更深层次的剖析发现:上一个拦截器intercept()方法中invocation.invoke()方法的返回值(注意:是invoke方法的返回值)都是该拦截器调用的下一个拦截器intercept()方法的返回值(注意是intercept方法的返回值)。
      特殊情况:最后一个拦截器的
    intercept()方法中invocation.invoke()方法的返回值 就是它调用Action中方法的返回值。
    其实如果拦截调用invocation.invoke方法,即调用了Action中的方法,毋庸置疑,他就是最后一个拦截器的第二种了!!!!
    由此可见,上图内涵很丰富呀!!!!
     
        所以,我们可以发现,invocation.invoke()这个方法其实是整个拦截器框架的实现核心。基于这样的实现机制,我们还可以得到下面2个非常重要的推论:
    1. 如果在拦截器中,我们不使用invocation.invoke()来完成堆栈中下一个元素的调用(即此拦截器就是最后一个拦截器,而且没有调用Action),而是直接返回一个字符串,那么Struts2将根据此字符串去寻找result中相应的视图显示,那么整个执行将被中止。呵呵,这也是Intercept接口中intercept方法有一个返回值的原因所在!!!

    2. 拦截器的分类:
      我们可以以invocation.invoke()为界,将拦截器中的代码分成2个部分:
      1)before拦截器:在invocation.invoke()之前的代码,将会在invocation.invoke()代码执行之前执行,即在Action之前被依次顺序执行
      2)after拦截器:在invocation.invoke()之后的代码,
    ,将会在invocation.invoke()代码执行之后执行,即在Action之后被逆序执行
       注意:
    invocation.invoke()代码执行后,也就是说:不仅执行类Action,也执行类Result。因而,等退回到拦截器的调用代码时,Result已经生成,View已经确定,这时你再修改模型(Action的属性)或请求对象的属性,对视图不会有任何影响。 

      3)PreResultListener拦截器:
        有的时候,before拦截和after拦截对我们来说是不够的,因为我们需要在Action执行完之后,但是还没有回到视图层之前,做一些事情。 Struts2同样支持这样的拦截,这种拦截方式,是通过在拦截器中注册一个PreResultListener的接口来实现的。
     
    下面是接口示例:
        public interface PreResultListener { 
             
            void beforeResult(ActionInvocation invocation, String resultCode); 
        }

    下面是拦截器注册示例:

      
      public String intercept(ActionInvocation invocation)
            throws Exception {
            invocation.addPreResultListener(new PreResultListener() {
                public void beforeResult(ActionInvocation invocation,
                                                String
    resultCode) {
                            /********** 要添加的代码  ***********/       
                }
            });
            String returnString = invocation.invoke();
            return returnString;
        }

    注意:
    在addPreResultListener里的异常,不会被Struts的框架捕获
    由此,我们就可以通过invocation.invoke()作为Action代码真正的拦截点,从而实现AOP。
       
    三.源码解析

        下面我们通过查看源码来看看Struts2是如何保证拦截器、Action与Result三者之间的执行顺序的。之前我曾经提 到,ActionInvocation是Struts2中的调度器,所以事实上,这些代码的调度执行,是在ActionInvocation的实现类中完 成的,这里,我抽取了DefaultActionInvocation中的invoke()方法,它将向我们展示一切
    // * @throws ConfigurationException If no result can be //found with the returned code
    public String invoke() throws Exception {

        String profileKey = "invoke: ";

        try {

         UtilTimerStack.push(profileKey);
         if (executed) {
          throw new IllegalStateException("Action has already executed");

         }
    // 依次调用拦截器堆栈中的拦截器代码执行
        if (interceptors.hasNext()) {

         final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();

         UtilTimerStack.profile("interceptor: "+interceptor.getName(),

         new UtilTimerStack.ProfilingBlock<String>() {

    public String doProfiling() throws Exception {

                             // 将ActionInvocation作为参数,调用interceptor中的intercept方法执行

         resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);

         return null;

    }
         });

         } else {

         resultCode = invokeActionOnly();

         }
         // this is needed because the result will be executed, then control will return to the Interceptor, which will

         // return above and flow through again

         if (!executed) {

                // 执行PreResultListener

         if (preResultListeners != null) {

         for (Iterator iterator = preResultListeners.iterator();

         iterator.hasNext();) {

         PreResultListener listener = (PreResultListener) iterator.next();
         String _profileKey="preResultListener: ";

         try {

         UtilTimerStack.push(_profileKey);

         listener.beforeResult(this, resultCode);

         }

         finally {

         UtilTimerStack.pop(_profileKey);

         }

         }

         }
       // now execute the result, if we're supposed to

                // action与interceptor执行完毕,执行Result

         if (proxy.getExecuteResult()) {

         executeResult();

         }
       executed = true;

         }
     return resultCode;

        }

        finally {

         UtilTimerStack.pop(profileKey);

        }
    }
      从源码中,我们可以看到Action层的4个不同的层次,在这个方法中都有体现,他们分别是:拦截器(Interceptor)、Action、PreResultListener和Result。在这个方法中,保证了这些层次的有序调用和执行。由此我们也可以看出Struts2在Action层次设计上的众多考虑,每个层次都具备了高度的扩展性和插入点,使得程序员可以在任何喜欢的层次加入自己的实现机制改变Action的行为。在这里,需要特别强调的,是其中拦截器部分的执行调用:
    resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this); 
    表面上,它只是执行了拦截器中的intercept方法,如果我们结合拦截器来看,就能看出点端倪来:
      public String intercept(ActionInvocation invocation) throws Exception {  

        String result = null;  

             before(invocation);  

            // 调用invocation的invoke()方法,在这里形成了递归调用 

          result = invocation.invoke();  

            after(invocation, result);  

           return result;  


    原来在intercept()方法又对ActionInvocation的invoke()方法进行递归调用,ActionInvocation循环嵌套在intercept()中,一直到语句result = invocation.invoke()执行结束。这样,Interceptor又会按照刚开始执行的逆向顺序依次执行结束。一个有序链表,通过递归调用,变成了一个堆栈执行过程,将一段有序执行的代码变成了2段执行顺序完全相反的代码过程,从而巧妙地实现了AOP。这也就成为了Struts2的Action层的AOP基础。
  • 相关阅读:
    Spring 中出现Element : property Bean definitions can have zero or more properties. Property elements correspond to JavaBean setter methods exposed by the bean classes. Spring supports primitives, refer
    java定时器schedule和scheduleAtFixedRate区别
    hql语句中的select字句和from 字句
    使用maven搭建hibernate的pom文件配置
    Failure to transfer org.apache.maven:maven-archiver:pom:2.5 from http://repo.maven.apache.org/ maven2 was cached in the local repository, resolution will not be reattempted until the update interv
    对于文件File类型中的目录分隔符
    hibernate的事务管理和session对象的详解
    解决mac 中的myeclipse控制台中文乱码问题
    ibatis selectKey用法问题
    Java中getResourceAsStream的用法
  • 原文地址:https://www.cnblogs.com/kabi/p/5182130.html
Copyright © 2011-2022 走看看