《Struts2技术内幕》 新书部分篇章连载(四)—— 核心分发器
程序员文章站
2022-03-02 21:08:49
...
9.2核心分发器 —— Dispatcher
9.2.1 核心分发器(Dispatcher)的核心驱动作用
Dispatcher之所以被称之为Struts2的核心分发器,主要是基于它在整个Struts2框架中的特殊地位。我们经常会使用“起-承-转-合”这4个不同的阶段来描述一个事件的整个过程,对于Struts2而言,Dispatcher实际上就是囊括这4个阶段的核心分发器。
9.2.1.1起 —— 负责系统初始化
Dispatcher在初始化时负责整个Struts2的初始化工作。在Dispatcher中,init()方法会在Dispatcher创建之初被调用,从而触发整个Struts2的初始化过程。所有的初始化方法在Dispatcher中都以init加上下划线作为起始进行命名,如图9-1所示:
从图中,我们可以看到init方法是一个public方法,是整个初始化过程中的操作窗口。而其余的所有以init加上下划线命名的方法,都将在init方法中以一定的顺序被依次调用。这些方法涵盖了在Dispatcher进行初始化过程中逻辑的方方面面。我们将在之后的源码分析中为读者详细解读。
9.2.1.2承 —— 接收并预处理Http请求
Dispatcher需要负责对Http请求进行预处理。这些预处理的过程主要包括:设置Encoding和Locale、对HttpServletRequest进行封装以及准备MVC运行的数据环境等。
这些预处理过程,由PrepareOperations在进行Http请求预处理的这个阶段调用执行。而我们知道PrepareOperations对象本身只是一个操作代理接口,真正完成逻辑功能的是Dispatcher本身。PrepareOperations在内部实现中所做的,只是对每个操作做了一个简单的转发处理。这些方法如图9-2所示:
注意在这其中的两个createContextMap方法,在这两个方法中,Dispatcher把Web容器相关的数据对象,封装成了普通的Java对象(实际上被封装成了Map对象)。这对于整个框架的意义不亚于任何其他任何的精妙设计。因为这两个方法的核心要义在于将Web请求数据进行“去容器化”处理,使得后续依赖于Web请求的任何操作,不再受限于任何的Web容器对象,从而真正做到了解耦合。读者可以结合在之前XWork章节中,我们对ActionContext初始化的相关描述,createContextMap方法实际上成为了ActionContext这个数据环境内部构造上下文环境的数据基础。
9.2.1.3转 —— 将Struts2转入XWork
在第三章中,我们提到Struts2在处理Http请求时还可以被分为2个阶段,而划分这2个阶段的临界点实际上就在于Dispatcher对象。
在第一个阶段中,所有的Http请求通过Dispatcher对象的Http请求的预处理,请求中与Web容器相关的对象全部被封装成与Web容器无关的对象,并构造出一个数据环境。于是,在第二个阶段中Dispatcher就能够将这一数据环境转发到XWork框架中执行,从而保证解耦在这一刻彻底完成。Dispatcher中进行请求转发的方法是serviceAction,如图9-3所示:
我们可以看到,serviceAction方法与Servlet规范中的service方法有着异曲同工之妙。只是在这里,serviceAction方法并不是Http请求处理的“终点”,反而是Struts2 / XWork中进行Http请求处理的“起点”和“中转站”。
serviceAction方法是整个Dispatcher对象的逻辑调度核心方法,也是整个Struts2在Http请求处理阶段的核心逻辑。我们将在下一章中对这个方法进行源码解读。
9.2.1.4合 —— 垃圾清理
Dispatcher不仅负责逻辑执行,还要负责在执行完毕之后进行对象清理。这一工作是由cleanup方法完成的,如图9-4所示:
在Dispatcher的cleanup方法中,将完成对Http请求的处理过程中产生的请求周期的对象的清理工作。我们在这里不再展开其源码的分析,不过我们可以将其源码中所涉及到的清理对象进行逻辑分类:
对于在整个请求周期中定义了完整的生命周期的框架元素的清理
例如,我们之前讲过的XWork中Interceptor对象,其接口定义中就蕴含了destroy方法。这个方法,就会在cleanup的流程中被调用执行。
对于线程安全的ThreadLocal对象的清理
我们知道,XWork中的ActionContext作为一个数据环境使用了ThreadLocal模式在整个当前线程共享数据,那么在整个请求结束时,与当前线程绑定的ActionContext的副本也在cleanup方法中被清理。
9.2.2 核心分发器(Dispatcher)的数据结构
Dispatcher中的内容如此丰富,以至于它成为了我们进行Struts2研究的最重要线索。之前我们已经看到了Dispatcher在不同阶段所承担的不同职责。接下来,我们从数据结构的角度来看看Dispatcher有什么独特之处。翻开Dispatcher的源码,我们可以看到Dispatcher内部的属性构成,源代码如代码清单9-3所示:
这个非常惹眼的内部变量instance,它不仅是一个静态的实例变量,同时它也被定义成为ThreadLocal类型,而ThreadLocal所承载的实际类型是Dispatcher本身!这正是一个典型的ThreadLocal模式的应用范例!
从这个数据结构的定义来看,我们可以确认,Struts2对Dispatcher应用了ThreadLocal模式。其中,Dispatcher本身就是实现ThreadLocal模式的核心要素,因为我们实际上在Dispatcher中看到了实现ThreadLocal模式的两大步骤。在之后的源码分析中,我们还将看到Dispatcher在处理Http请求时与ThreadLocal变量的交互过程。
使用ThreadLocal模式的初衷是为了解决多线程环境下对象访问的线程安全问题。Dispatcher作为核心分发器,成为了入口程序StrutsPrepareAndExecuteFilter中的实例变量PrepareOperations和ExecuteOperations的实际操作句柄(Dispatcher是它们的重要实例变量),从而被所有的线程共享访问。因此,Dispatcher被打造成ThreadLocal模式是一个理所当然的选择。
然而,这一理所当然的选择,却使得Struts2能够摆脱传统的表示层框架对于Web容器的高度依赖。由此可见,ThreadLocal模式是贯穿整个Struts2的核心技术之一,成为了Struts2将Web容器与Java开发解耦合的内在基础。不仅如此,我们还将在之后的章节中看到ThreadLocal模式在Struts2数据流转的过程中所起到的决定性作用。
最后,有两个针对Dispatcher重要结论需要强调一下:
这个结论所针对的角度是Dispatcher的内容和职责,将它牢记于心,有助于日后我们对Struts2相关问题的调试和分析。实际上,对于Struts2的运行机理的一切研究,都离不开Dispatcher这个核心分发器。Dispatcher中所包含的所有方法,从一个侧面反应了Struts2框架的几个主要功能,而Dispatcher则处于一个“居中调度”的重要位置。
这个结论则从另外一个角度描述了Dispatcher在整个Struts2框架中的重要地位。它也成为了Struts2实现“解耦合”的核心所在。我们将在Struts2的第二条主线的分析中看到这个分界点以及它在框架中的逻辑意义。
9.2.1 核心分发器(Dispatcher)的核心驱动作用
Dispatcher之所以被称之为Struts2的核心分发器,主要是基于它在整个Struts2框架中的特殊地位。我们经常会使用“起-承-转-合”这4个不同的阶段来描述一个事件的整个过程,对于Struts2而言,Dispatcher实际上就是囊括这4个阶段的核心分发器。
9.2.1.1起 —— 负责系统初始化
Dispatcher在初始化时负责整个Struts2的初始化工作。在Dispatcher中,init()方法会在Dispatcher创建之初被调用,从而触发整个Struts2的初始化过程。所有的初始化方法在Dispatcher中都以init加上下划线作为起始进行命名,如图9-1所示:
从图中,我们可以看到init方法是一个public方法,是整个初始化过程中的操作窗口。而其余的所有以init加上下划线命名的方法,都将在init方法中以一定的顺序被依次调用。这些方法涵盖了在Dispatcher进行初始化过程中逻辑的方方面面。我们将在之后的源码分析中为读者详细解读。
9.2.1.2承 —— 接收并预处理Http请求
Dispatcher需要负责对Http请求进行预处理。这些预处理的过程主要包括:设置Encoding和Locale、对HttpServletRequest进行封装以及准备MVC运行的数据环境等。
这些预处理过程,由PrepareOperations在进行Http请求预处理的这个阶段调用执行。而我们知道PrepareOperations对象本身只是一个操作代理接口,真正完成逻辑功能的是Dispatcher本身。PrepareOperations在内部实现中所做的,只是对每个操作做了一个简单的转发处理。这些方法如图9-2所示:
注意在这其中的两个createContextMap方法,在这两个方法中,Dispatcher把Web容器相关的数据对象,封装成了普通的Java对象(实际上被封装成了Map对象)。这对于整个框架的意义不亚于任何其他任何的精妙设计。因为这两个方法的核心要义在于将Web请求数据进行“去容器化”处理,使得后续依赖于Web请求的任何操作,不再受限于任何的Web容器对象,从而真正做到了解耦合。读者可以结合在之前XWork章节中,我们对ActionContext初始化的相关描述,createContextMap方法实际上成为了ActionContext这个数据环境内部构造上下文环境的数据基础。
9.2.1.3转 —— 将Struts2转入XWork
在第三章中,我们提到Struts2在处理Http请求时还可以被分为2个阶段,而划分这2个阶段的临界点实际上就在于Dispatcher对象。
在第一个阶段中,所有的Http请求通过Dispatcher对象的Http请求的预处理,请求中与Web容器相关的对象全部被封装成与Web容器无关的对象,并构造出一个数据环境。于是,在第二个阶段中Dispatcher就能够将这一数据环境转发到XWork框架中执行,从而保证解耦在这一刻彻底完成。Dispatcher中进行请求转发的方法是serviceAction,如图9-3所示:
我们可以看到,serviceAction方法与Servlet规范中的service方法有着异曲同工之妙。只是在这里,serviceAction方法并不是Http请求处理的“终点”,反而是Struts2 / XWork中进行Http请求处理的“起点”和“中转站”。
serviceAction方法是整个Dispatcher对象的逻辑调度核心方法,也是整个Struts2在Http请求处理阶段的核心逻辑。我们将在下一章中对这个方法进行源码解读。
9.2.1.4合 —— 垃圾清理
Dispatcher不仅负责逻辑执行,还要负责在执行完毕之后进行对象清理。这一工作是由cleanup方法完成的,如图9-4所示:
在Dispatcher的cleanup方法中,将完成对Http请求的处理过程中产生的请求周期的对象的清理工作。我们在这里不再展开其源码的分析,不过我们可以将其源码中所涉及到的清理对象进行逻辑分类:
对于在整个请求周期中定义了完整的生命周期的框架元素的清理
例如,我们之前讲过的XWork中Interceptor对象,其接口定义中就蕴含了destroy方法。这个方法,就会在cleanup的流程中被调用执行。
对于线程安全的ThreadLocal对象的清理
我们知道,XWork中的ActionContext作为一个数据环境使用了ThreadLocal模式在整个当前线程共享数据,那么在整个请求结束时,与当前线程绑定的ActionContext的副本也在cleanup方法中被清理。
9.2.2 核心分发器(Dispatcher)的数据结构
Dispatcher中的内容如此丰富,以至于它成为了我们进行Struts2研究的最重要线索。之前我们已经看到了Dispatcher在不同阶段所承担的不同职责。接下来,我们从数据结构的角度来看看Dispatcher有什么独特之处。翻开Dispatcher的源码,我们可以看到Dispatcher内部的属性构成,源代码如代码清单9-3所示:
public class Dispatcher { // 这里省略了许多其它代码 /** * 提供了一个静态的ThreadLocal变量 */ private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>(); /** * 提供一个接口方法,用于获取当前线程安全的dispatcher实例 * * @return */ public static Dispatcher getInstance() { return instance.get(); } /** * 将dispatcher实例绑定到当前线程 * * @param instance */ public static void setInstance(Dispatcher instance) { Dispatcher.instance.set(instance); } }
这个非常惹眼的内部变量instance,它不仅是一个静态的实例变量,同时它也被定义成为ThreadLocal类型,而ThreadLocal所承载的实际类型是Dispatcher本身!这正是一个典型的ThreadLocal模式的应用范例!
从这个数据结构的定义来看,我们可以确认,Struts2对Dispatcher应用了ThreadLocal模式。其中,Dispatcher本身就是实现ThreadLocal模式的核心要素,因为我们实际上在Dispatcher中看到了实现ThreadLocal模式的两大步骤。在之后的源码分析中,我们还将看到Dispatcher在处理Http请求时与ThreadLocal变量的交互过程。
使用ThreadLocal模式的初衷是为了解决多线程环境下对象访问的线程安全问题。Dispatcher作为核心分发器,成为了入口程序StrutsPrepareAndExecuteFilter中的实例变量PrepareOperations和ExecuteOperations的实际操作句柄(Dispatcher是它们的重要实例变量),从而被所有的线程共享访问。因此,Dispatcher被打造成ThreadLocal模式是一个理所当然的选择。
然而,这一理所当然的选择,却使得Struts2能够摆脱传统的表示层框架对于Web容器的高度依赖。由此可见,ThreadLocal模式是贯穿整个Struts2的核心技术之一,成为了Struts2将Web容器与Java开发解耦合的内在基础。不仅如此,我们还将在之后的章节中看到ThreadLocal模式在Struts2数据流转的过程中所起到的决定性作用。
最后,有两个针对Dispatcher重要结论需要强调一下:
downpour 写道
结论 Dispatcher作为一个线程安全的对象,涵盖了整个Struts2的生命周期。无论是Struts2的初始化,还是处理Http请求,实际都在Dispatcher中完成。
这个结论所针对的角度是Dispatcher的内容和职责,将它牢记于心,有助于日后我们对Struts2相关问题的调试和分析。实际上,对于Struts2的运行机理的一切研究,都离不开Dispatcher这个核心分发器。Dispatcher中所包含的所有方法,从一个侧面反应了Struts2框架的几个主要功能,而Dispatcher则处于一个“居中调度”的重要位置。
downpour 写道
结论 Dispatcher是Struts2与XWork的分界点,也是将MVC实现与Web容器隔离的分界点。
这个结论则从另外一个角度描述了Dispatcher在整个Struts2框架中的重要地位。它也成为了Struts2实现“解耦合”的核心所在。我们将在Struts2的第二条主线的分析中看到这个分界点以及它在框架中的逻辑意义。
推荐阅读
-
《Struts2技术内幕》 新书部分篇章连载(二)—— 面向对象浅谈
-
《Struts2技术内幕》 新书部分篇章连载(四)—— 核心分发器
-
《Struts2技术内幕》 新书部分篇章连载(三)—— 多视角透析Struts2
-
《Struts2技术内幕》 新书部分篇章连载(七)—— ThreadLocal模式
-
《Struts2技术内幕》 新书部分篇章连载(六)—— 框架的本质
-
《Struts2技术内幕》 新书部分篇章连载(五)—— 请求响应哲学
-
《Struts2技术内幕》 新书部分篇章连载(八)—— XWork容器概览
-
《Struts2技术内幕》 新书部分篇章连载(十)—— XWork概览
-
《Struts2技术内幕》 新书部分篇章连载(九)—— 强大的OGNL
-
《Struts2技术内幕》 新书部分篇章连载(一)—— 如何学习开源框架