`
yangw101860
  • 浏览: 35998 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
最近访客 更多访客>>
社区版块
存档分类
最新评论

Struts2请求分派源码解读

阅读更多

       最近在改造公司内部JavaEE应用平台的架构设计,阅读了大量的spring和struts2的源码。网上的struts2方面的源码研究比较少,而且零散不够具体,因此将我在struts2阅读过程中的一些理解用描述性的文字反应出来。以备具体阅读源代码时参考。

       其中,可能会有些不够准确的地方,欢迎指出。转载请注明出处:wit yang(ffyang101860@hotmail.com)。

 

 

Struts2请求分派由filter完成。目前提供了两种不同的运用情况:

StrutsPrepareAndExecuteFilter完成了原FilterDispatcher的功能,使用该filter时必须把其配置在所有filter的最后。但是,无法满足这样一种应用场景:希望在struts2的环境下做一些过滤器的操作。因此strutsPrepareFilter+StrutsExecuteFilter用于满足上述的需求。它将环境的加载和分派执行action分开来。

strutsPrepareFilter主要功能即在init中根据filterConfig初始化了Dispatcher(该类是真正的请求分派功能实现类),并由此Dispatcher的初始化触发了框架的初始化工作。另外,解析出一个需要过滤的请求后缀列表以及调用一个待扩展的回调方法。然后在doFilter中,也即截获到请求并处理之前完成了如下的处理:设置编码及Local,创建ActionContext,绑定当前线程与dispatcher,如果非过滤请求则包装request(这个包装在FileUploadIntercepter中有很好的应用),解析出actionmaping

StrutsExecuteFilter主要功能即在doFIlter中执行了指定的action。流程如下:如果是过滤的请求直接chain.doFilter。获取prepareFilter中准备好的环境。如果是静态资源的请求则使用静态资源加载器处理静态资源。否则executeAction。值得一提的是,在这里的静态资源判断的条件与PrepareAndExecuteFilter相比多了一个recursionCounter>1的条件,作者的提示指出该条件意味着当前是在一个forward的执行过程中,这个时候request里会残留着在forward前解析的mapping,如果处理将会导致死循环。该条件意味着forward过程不难理解,现在需要明白的是为什么PrepareAndExecuteFilter里同样的判断没有这个条件呢?它也应该会面临着forword时重复mapping的问题?问题的关键在于findActionMapping方法的最后一个参数forceLookup(抛弃request残留)。ExecuteFilter为了降低开销直接得到PrepareFilter中解析出来的mapping,而将forceLookup设置为了false

在上述描述中,涉及到两个关键的过程:

Dispatcher的初始化:构建一个ConfigurationManager实例。实例中维护了一个List内容ContainerProvider,该类实际上可以看作是一个不同种类的资源加载器。接下来,为刚才构建的ConfigurationManager实例添加一系列的ContainerProvider。这个过程可以理解为,注册哪些即将加载的配置资源。它们包括了struts-default.xml,struts-plugin.xml,struts.xml等缺省配置也包括用户自定义的资源以及系统初始化用到的标准对象。注册完毕后,即可以通过刚才的ConfigurationManager实例获得Configuration对象。这个创建过程比较复杂,过程如下,创建一个ContainerBuilderContainerProperties实例,以其作为参数调用所有的ContainerProviderregister方法(这就是具体的配置元素解析逻辑)。这个过程会为ContainerBuilder加载一系列的factories。而这些factories记录了各种配置元素包括常量属性的创建方法。在这些个factories加载后,还要为ContainerBuilder加载一个关于Configuration类的factory,实际上,这个factory指明创建一个DafaultConfiguration实例。接下来,创建一个bootstart用途的Container(需要说明的是,由于该容器是单例方式的,所有这里加载后,实际上起到了预加载作用),其中加载了诸如ObjectFactory以及ValueStackFactory等核心类的实例。再用前面创建的ContainerBuilder创建一个Container,这个Container也即是加载了当前Struts2所有客户配置的一个IOC容器。利用该IOC容器获得一个ObjectFactory的实例(通过getInstance方法,该方法根据指定的type找到对应的factory,然后调用其creat方法创建对象实例)。然后,获得所有的PackageProvider并使用IOC进行依赖注入。重建运行时配置。在以上结束后,执行finally逻辑,如果oldContextnull则取消刚才绑定的ActionContext。至此整个获得Configuration对象的过程结束。通过Configuration对象可以获得刚才创建的IOC容器container。用它对当前的Dispacher进行依赖注入(调用inject,该方法会为给定的对象的methodfield注入数据,数据来源container内)。整个过程的最后,会给予监听器DispatcherListener执行的机会。

executeAction过程:首先创建一个action context(注:不是ActionContext,它实际上是ActionContext里维护的context变量的内容。在创建ActionContext时,主要的过程就是创建这个action context),这个context封装了servlet环境相关信息。如果ActionContext中已有一个ValueStack存在,则将其copycontext中。(注:实际上整个过程就是一个完整的ActionContextcontext’copy过程,它用于生成代理所以不能直接从ActionContext中获取引用。)之后还需要获得一些其他参数,然后利用刚才得到的context对象去构建一个DefaultActionInvocation实例,接着用container依赖注入,实例中的所有@Inject注解的方法将被注入对象。 随后,即开始了创建ActionProxy的过程。这个过程也比较复杂,简单描述来说即,将以上获得的所有参数一起,构建一个DefaultActionProxy实例,并依赖注入之。最后调用ActionProxyprepare方法,至此完成整个ActionProxy的创建。接下来的工作就是利用ActionProxy来执行action的过程。如果处理失败将会发送一个Http的错误响应代码。ActionProxy实际上作为Xwork核心和action层之间的代理层而定义,为RMI或者SOAP等不同形式的action应用提供了可能。在DefaultActionProxy最主要的实现了两个方法,一个是prepare方法,另一个是excute方法。prepare方法简单的来说即:创建出一个ActionConfig实例、计算出应该执行的methodname、执行其内的ActionInvocationinit方法。excute方法很简单:置换当前的ActionContext内容为InvocationContext内容,然后执行其内的ActionInvocation实例的invoke方法。最后根据需要换回原来的ActionContext。通过上面的分析,发现,所有的关键操作都跟ActionInvocation联系着。

        ActionInvocation分析Invocation顾名思义,它是一个action执行者,它定义并控制了action的执行过程。其通过持有的ActionProxy以及Intercepters来完成一个action的执行调用。init方法执行过程:获得当前线程的ActionContext实例并将当前的ActionInvocation实例置入。创建一个contextMap,创建action实例并将其植入contextMapstack中。利用此map创建InvocationContext。最后初始化intercepters链。Invoke方法是最重要的一个方法,但它的实现并不复杂:首先轮询intercepters,调用其intercept方法,直到最后一个拦截器被调用执行,这时最后一个拦截器的invocation.invoke();会导致再次调用invoke方法,而这个时候的拦截器已经全部轮询完,所以开始执行Action,这个过程会利用反射去调用指定的方法,并得到一个返回结果(字符串或者Result对象);接下来就是结果的执行流程,这个过程首先会给予PreResultListener执行机会,然后根据刚才的返回结果(如果是字符串则需要根据Result配置生成实例,例如“success”结果)调用Result的执行方法。在结果执行完成后,这个时候会开始执行最后一个拦截器的invocation.invoke();后的代码,然后依次是其他拦截器,等都执行完后,这个时候的流程又会回到结果的执行流程开始处。所以使用了一个executed标记来记录Result是否已经执行了

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics