深入struts2_控制流体系

作者: fredal | 来源:发表于2016-04-28 16:40 被阅读263次

    1. Action

    • 1.1 请求相应模式

    请求-响应模式是一种概念非常宽泛的人机交互模式,是人与计算机进行沟通的一种最基本的行为方式.
    那么在web应用开发过程中都是如何实现的呢.我们会有三种不同的实现方式.
    参数-返回值
    这是一种最为直观的请求-响应的实现模式.方法参数对应请求内容,返回值对应结果响应.
    参数-参数
    最底层的Servlet就是基于此模式设计的,而实际上底层规范都不得不采用这样的实现机制.
    PoJo
    这也就是struts2所采用的请求相应模式了,所有的请求对象都以内部属性变量的方式存在,而且所有的返回值同样是以该形式存在.相当于,POJO对某一次的响应是一个有状态的响应.

    • 2. struts2与springMVC的解决方案

    springMVC采用了一种游离于参数-参数模式与参数-返回值模式之间,并整合了各自优势的实现模式,而struts2则是POJO模式.从三个方面比较各自优劣:
    请求参数:在这一点上参数比属性变量更为直观,更符合java原生的语法含义.springMVC略胜一筹.
    响应数据:对于纯粹的响应数据,最适合的仍然是方法的返回值.然而响应必须包含响应数据以及响应跳转逻辑,而框架的困境在于很难同时表达数据和控制两个方面.SpringMVC使用一个ModelAndView的对象将不同的逻辑处理分开,使用返回值来表达响应流程控制,而为响应方法增加一个Model对象,用以存储数据.Struts2就简单很多,仍然可以用内部属性存储数据.
    所以这个方面很难讲孰优孰劣.
    响应控制:在上面讲了,springMVC使用ModelAndView处理数据和控制,而struts2直接用返回值处理响应控制即可.
    总得来说,在数据访问上struts2有天然的优势.

    • 3. action的突破

    回到action本身,action实现了对servlet的两大突破.
    与web容器无关
    关于这一点,显而易见我们看不到任何和web容器有关的对象.action只是一个普通的java对象.这使得action非常易于测试,看上去更像一个POJO,易于管理,并且使得struts2在控制器这个层面上有更多的发挥余地.
    响应对象有状态
    action对象表现出两种截然不同的特性,即属性特征与行为特征.从语法角度讲,前者指变量的属性,而后者指的是响应的过程.action是一个状态与动作的结合体,天然符合了响应类的基本要素.action的状态自然解毒为请求数据与响应数据.

    2. Interceptor

    • 1.AOP编程

    Interceptor本身就是属于AOP的概念,基本实质为一个代码段,通过定义切入点,来指定Interceptor的代码逻辑在切入点之前或之后执行,从而起到"拦截"的作用.
    暂且不去说AOP的基本概念的含义,但我们可以看到struts2中的Interceptor与AOP之间的对应
    切面Aspect:Interceptor实现
    通知Advice:Around通知
    切入点Pointcut:Action对象
    连接点Joinpoint:Action执行

    • 2. 内部结构与执行方式

    对于Interceptor的内部结构,有几点讲
    群居:Interceptor对象是一个群居对象,在同一时刻总有多个对象协同工作.
    栈结构:这是一个典型的栈结构,而Action位于栈的底部,由于后入先出,所以action才最后执行.那么也许有疑问,不是action之后还要倒着再来一遍Interceptor么.其实这里是用递归调用完成的一个巧妙数据结构.
    关于执行方式,来看其一个实现类的源码

      public String intercept(ActionInvocation invocation) throws Exception{
          return invocation.invoke();   
      }
    

    intercept方法以ActionInvocation作为参数,我们知道它是XWork控制流元素的核心调度元素,以它为参数方便自身与其他元素的交互,其次也便于ActionInvocation对自己的调度.
    再来看方法的主体,只有一句invocation.invoke().这里有两个含义,其一是有责任把执行的控制权交给栈的下一个元素去执行.其二是如果返回一个String类型的Result就中止栈的调度.

    • 3. AOP还是IOC

    Spring是AOP的一个典范,可以以一句话总结:Spring是用IOC实现AOP的.
    那么回到XWork的Interceptor上,它的AOP实现与Spring的差别在于对于切入点的定于不同.Spring侧重于方法级别的AOP拦截,而XWork针对Action对象的拦截.
    拦截Action对象之后干嘛呢,对其内部属性变量进行管理,影响其运行状态的改变,从而对其行为进行扩展.
    换个角度看,内部属性与方法有啥用,构成Action这个响应类与外部环境的交互接口,构成请求数据与响应数据,构成自身行为动作与业务逻辑处理对象之间的交互协作.而这正是依赖关系的体现(环境依赖,数据依赖,协作依赖).
    也就是XWork使用AOP实现IOC的.

    • 4. Interceptor的意义

    可以从不同的角度看到Interceptor存在的价值.
    Interceptor对象的引入,是对事件处理过程中主次职责的有效划分
    Interceptor对象的引入能丰富整个事件处理流程的执行层次,为AOP编程打下结实的数据结构基础
    Interceptor使得责任链模式在整个执行栈的调度过程中顺利实施

    3. ActionInvocation

    ActionInvocation是控制流的核心调度元素.
    来看其源码

      public interface ActionInvocation extends Serializable{
       Object getAction();
       boolean isExecuted();
       ActionContext getInvocationContext();
       ActionProxy getProxy();
       Result getResult() throws Exception;
       String getResultCode();
       void setResultCode(String resultCode);
       ValueStack getStack();
       void addPreResultListener(PreResultListener listener);
       String invoke() throws Exception;
       String invokeActionOnly() throws Exception;
       void setActionEventListener(ActionEventListener listener);
       void init(ActionProxy proxy) ;
     }
    

    大致可以根据接口方法的不同作用进行逻辑分类
    对控制流元素和数据流元素的访问接口
    getAction,getActionProxy,getStack等等
    对执行调度流程的扩展接口
    addPreListner,setActionEventListner等等
    对执行栈进行不调度执行的接口
    invoke,invokeActionOnly;

    • 3.1 调度分析

    ActionInvocation的核心调度功能是通过invoke方法实现,对Interceptor和Action对象共同构成的执行栈进行逻辑执行调度.
    看看源码中的invoke()方法

       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();
                   String interceptorMsg = "interceptor: " + interceptor.getName();
                   UtilTimerStack.push(interceptorMsg);
                   try {
                                   resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
                               }
                   finally {
                       UtilTimerStack.pop(interceptorMsg);
                   }
               } 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) {
                   if (preResultListeners != null) {
                       for (Object preResultListener : preResultListeners) {
                           PreResultListener listener = (PreResultListener) preResultListener;
    
                           String _profileKey = "preResultListener: ";
                           try {
                               UtilTimerStack.push(_profileKey);
                               listener.beforeResult(this, resultCode);
                           }
                           finally {
                               UtilTimerStack.pop(_profileKey);
                           }
                       }
                   }
    
                   // now execute the result, if we're supposed to
                   if (proxy.getExecuteResult()) {
                       executeResult();
                   }
    
                   executed = true;
               }
    
               return resultCode;
           }
           finally {
               UtilTimerStack.pop(profileKey);
           }
       }
    

    我们可以看到如果有下一个拦截器,会不断交给下一个执行,如果没有拦截器了就执行action,,最后执行result的对象逻辑.那么action执行完毕之后倒序的拦截器去哪了呢?
    这里就要说递归的巧妙运用,以invocation.invoke为分水岭,递归使得分水岭之后的代码被暂时封存,而去执行栈的下一个元素,等完整的递归调用结束之后,再触发整个逆序的执行过程.

    1

    4. ActionProxy

    ActionProxy是Xwork事件处理框架的总代理

     public interface ActionProxy {
    
       Object getAction();
    
       String getActionName();
    
       ActionConfig getConfig();
    
       void setExecuteResult(boolean executeResult);
    
       boolean getExecuteResult();
    
       ActionInvocation getInvocation();
    
       String getNamespace();
    
       String execute() throws Exception;
       String getMethod();
    
       boolean isMethodSpecified();   
    }
    

    看源码可知,ActionProxy也是一个复杂的接口,身处比ActionInvocation更高的层次,主要职责是维护Xwork执行元素与请求对象直接的映射关系.
    再来看ActionProxy的初始化,同样是由工厂类实现,在工厂类接口中找到createActionProxy方法

     public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext);
    

    可以看到需要的参数包括
    配置关系映射:namespace,actionName,methodName等等..
    运行上下文环境:extraContext,cleanupContext

    5. 数据流与控制流的交互

    2
    这张图在讲架构时候类似地出现过.而这里着重于数据流与控制流的交互上.
    首先ActionContext横跨了整个Xwork的控制流架构,意味着它的生命周期有那么长.由于ActionContext使用Threadlocal模式实现,也表明它甚至对整个线程都是有效的.
    再来看ValueStack,它环绕着Action存在,可以说action是数据流与控制流的交互点.看源码便能一目了然
     public void init(ActionProxy proxy){
      if(pushAction){
         stack.push(action);
         contextMap.put("action",action);
      }
     }
    

    而action的交互体系分为三部分即外部环境交互体系,数据交互体系,协作交互体系.
    第一个最主要指action与web容器中对象的交互过程.一方面可以使用servletActionContext实现,另一方面也可以使用ServletConfigInterceptor等拦截器通过IOC注入实现.
    第二个就是指与数据流元素的交互了,请求数据,响应数据与属性变量的映射关系,主要由ParametersInterceptor拦截器完成.
    第三个指将struts2看成一个门户,获得业务逻辑操作对象的过程,例如与Spring的整合由ActionAutowiringInterceptor完成.

    6. result

    作为控制流元素的最后一个,result是非常特殊的.一方面接口定义在XWork体系中,但实现类确应算Struts2体系之中,它告诉程序该何去何从.
    action似乎把HttpServletResponse的职责都承包了,接口与实现的分离实现了框架级别的解耦合,为control层与view层架起桥梁.
    如果说action本身并不实现解耦,而是通过数据流或者AOP拦截器等手段加以实现,那么result本身就是起到了解耦的作用.
    仔细思考result的职责,主要有三个:
    起到一个中转作用,实现控制权的转移:即从Xwork到Struts2的转移
    需要进行两大框架的解耦:从struts2的角度看,这是一个典型的策略模式.
    需要一个web容器的代理操作对象:屏蔽底层的细节.

    相关文章

      网友评论

        本文标题:深入struts2_控制流体系

        本文链接:https://www.haomeiwen.com/subject/ugdhrttx.html