当Web请求到达服务器时,请求就会被Servlet容器(Tomcat)派发到相应的Servlet中去,而调用这些Servlet之前,满足一定条件的Filter也会被调用,我们之前在web.xml中配置的StrutsPrepareAndExeuteFilter就属于满足条件的Filter,因为它会拦截所有URL。当Filter被Servlet容器调用时,doFilter方法就开始执行了。我们现在prepare.setEncodingAndLocale(request, response);这一行打一个断点,这样就可以观察到每一个步骤的细节问题。
从字面上理解prepare.setEncodingAndLocale、prepare.createActionContext和prepare.assignDispatcherToThread的作用分别是用于处理编码与本地化、Action关联文信息、关联当前的Dispatcher与当前线程。
关于编码与本地化处理相关内容请参考源码与下图来理解:
方法createActionContext的作用就是创建一个与当前请求相关的ActionContext对象,后续的请求处理操作都会从这里取得相关信息。至于它到底把哪些东西进行了处理,是如何处理的,不在这里讲解,只说一下与ActionContext相关的技术问题。Struts1的一个缺陷就是Action是非线程安全的,也就是说,如果我们在Action中定义了共享资源,在并发请求时就可能会出现资源访问冲突的现象。举个例子来说,比如在Action中定义一个私有成员userName,这个userName每次都会从request中取得数据,当两个请求A和B先后到达后,B所得到的内容就会覆盖之前A所写入的内容,原因就是Action在内存中是唯一的,userName这个属性是共享!那么如何让两次请求可以互相独立呢,Struts2中的ActionContext就可以解决这个问题。
其实ActionContext并不是什么高级货,我们同样可以在Struts1中编写自己的ActionContext。下面我们详细解读一下ActionContext的源码。先看一下类图:
从类图上可以看出,ActionContext总共有两个部分,第一部分就是静态常量以及成员变量的定义,第二部分为操作方法定义。actionContext是一个精通的包级访问ThreadLocal类型变量,也就是说它仅对同包内的访问开放,拒绝其他访问。ThreadLocal在多线程访问中是一个很重要的概念,它将存储与每个线程管理起来,回顾一下上面A、B请求的例子,因为它们存放内容都在同一处,所以才会产生数据被覆盖的情况。而有了ThreadLocal之后,A可以把数据保存在与A线程相关的地方,B可以把数据保存在与B线程相关的地方,互不干扰。但为了能在一个地方储存更多的信息,Map结构可能就成为我们的首选数据结构了,所以类图中context就是一个Map类型的变量。存取数据时,最常用的就是get和put方法,而其他操作方法则是对Web程序的一种特定封装。(如果不了解ThreadLocal的话,先参考相关的JavaDoc,之后再来阅读ActionContext源码会收获更多)
如果真的明白了ActionContext的话,prepare.assignDispatcherToThread也就可以理解了,只是为当前Action要访问的Dispatcher找一个临时住所而已。
excludedPatterns就是让StrutsPrepareAndExecuteFilter跳过某些URL不做后续的Struts处理,它的用法请参考Struts2相关文档说明。假设现在满足excludedPatterns的条件,那程序就会走入chain.doFilter中,也就是说,我们配置的Struts2实际上什么都没干,纯粹的一个Filter而已,没有使用到Struts2的任何功能。
由于ActionContext和Dispatcher都使用到ThreadLocal,所以必须手动清理本次请求相关的内容,否则可能会因资源耗尽而产生内存溢出。具体的细节还是得参考ThreadLocal相关的文档。
请求处理 -StaticContentLoader VSActionMapping
在正式进入处理之前,Struts2先对request对象进行包装:
下面为Dispatcher.wrapRequest方法实现。主要与文件上传有关,如果对文件上传原来与实现有兴趣,可以参考http://www.servlets.com/和http://www.servlets.com/cos/:
接下来就要正式进入Struts2流程控制的核心了,这些核心组件会在ActionMapping所提供的信息下互相协作,完成请求处理。ActionMapping从哪里来呢?深入PrepareOperations.findActionMapping就会发现,它是从Dispatcher的Container中取出的,进一步分析Dispatcher的Container,我们就会发现,实现的核心内容是依赖于xwork的ConfigurationManager,这部分内容在下节会进行详细分析,这里只需要知道ActionMapping是根据xwork的ConfigurationManager读取struts.xml返回的就足够了。ActionMapping返回后,处理就被分为两部分,根据当前所请求的URL,如果ActionMapping找到时,请求就被转发至Action处理,否则会根据静态配置信息来尝试取得静态资源。
当以下面形式配置静态内容时,demo里所存放的内容就会被直接访问,这个打破了WEB-INF中内容不允许外部访问的常规。访问URL为:http://your_site:port/static/demo/your_static_resource
当ActionMapping被成功的访问且不为空时,Action的处理就正式开始了。调用情况如下,基本上是利用xwork的功能,我们在下节,详细解读一下xwork的工作原理。