关于xwork(struts2)拦截器体系的思考与分析

来源:互联网 发布:大数据 crm 市场规模 编辑:程序博客网 时间:2024/04/30 05:27
 

在xwork它的AOP体系在整个框架中占有非常重要的部分,实现许多非常好用的功能,例如参数设置,文件上传,字段检验等等.在xwork中AOP提供服务的组件称为拦截器,它们对需要执行的Action与Result进行了拦截封装,这种包装形式比较死板,比起spring中的AOP模式,当然是没法比的,但在xwork这个具体框架中,这种拦截模式还是十分有意义的.所有的拦截器通过固定接口来进行定义,最终所有拦截器被串在一个链上,最后执行的Action与Result在链的末尾.在xwork中AOP实现并没有使用代理,而是通过一个称之为ActionInvocation对象来进行组织与实现的.关于拦截器包装Action与Result的一个大致流程我就不想说了,网上到处都是.

由于xwork的它并不是AOP框架,因此在xwork中的拦截体系只AOP设计模式的一个小小应用,不使用动态代理,我想可能使它效率会更高一些,实际这个拦截体系统的一个重要实现类为DefaultActionInvocation,它通过一个精心组织的方法交替调用来实现,具体的代码如下: 

    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整个执行流程的分析特别不容易理解,最关键的代码在if (interceptors.hasNext()) {....这个代码块(我将它称之为代码块1)与if (!executed) {这个代码块(我将它称之为代码块2)这个两个代码是理解整个运行流程的关键.

首先需要明确一点就是DefaultActionInvocation是有状态的,框架会为每一个请求创建一个调用上下文对象DefaultActionInvocation,它有一个重要的功能就是记录拦截器的调用情况,有哪些已经调用,有哪些还没有调用,这就是代码块1的判定依据.通常情况下DefaultActionInvocation的此方法会与每一个拦截器的interceptor方法进行交替的调用,但也不是绝对这样,有些特别拦截器也可以决定不调用后续的拦截器与最终的Action与Result对象,但这种情况比较少见.所以这里先分析这种最正常的情况.

正常情况下会由ActionProxy对象调用DefaultActionInvocation的invoke方法,也就是上面这个方法,紧接着会进入拦截器的调用(DefaultActionInvocation会将自己作为参数传递),页拦截器通常又会回调调用上下文的invoke方法.因此就形成一个如下的调用链:

invoke->interceptor-invoke->interceptor->......(没有拦截器了)...->resultCode = invokeActionOnly();->Result#execute....................返回到各个拦器

在上面的调用链中需要特别的注意:就是所有拦器的代码有可能都会被调用一遍,但是DefaultActionInvocation#invoke中的代码却存在大量的冗余,冗余在哪里呢,就在代码块2中的因为在这块代码第一次被执行后,它将会被打上已经执行的标记,以后就不会再执行了.因此这段代码最有可能被执行的地点为最后一个拦截器调用DefaultActionInvocation#invoke方法时被执行,之后控制权限将又依次返回了交替的interceptor与invoke方法,此时invoke的执行已经被太大意义,因为Result已经执行,其实对于大多数据拦截器也已经没有太大的意义,但这不是所有情况.只是说大多数情况下在action执行之前拦截器要做事情往往比之后要多.大多数在两个核心的对象执行之后主要做一些统计,日志之类的工作.实际你可以去查看所有拦截器,基本上实质有用的功能都是在Action与Result对象执行之前所做的,执行之后只做一些锦上添花的工作.

上面是正常流程下整个拦截器的运行流程,如果说出现异常那就不一样了此时的代码块2将很难得到执行,除非在某个拦截器中对异常进行了处理,并返回了一个有效的resultCode接着在调用此拦截器的上一个invoke方法中会使得代码块2得以执行,即Result对象得到执行.

如果想在Action执行完成之后,通过Action的执行情况,对返回的结果有所控制,你就必须注册PreResultListener监听器,注册的对象是DefaultActionInvocation,而注册的最佳地点一般是在一些拦截器中,比如DebuggingInterceptor

原创粉丝点击