第5章 生命之源 —— XWork中的容器
对象的生命周期管理在基于面向对象的编程语言中是一个永恒的话题。从语法上讲,面向对象的高级编程语言都是以“对象”为中心的。而对象之间的继承关系、嵌套引用关系所形成的对象树结构为我们进行对象级别的逻辑操作提供了足够的语法支持。但这样一来,对象之间所形成的复杂关系也就为对象生命周期的管理带来了问题:- 在程序的运行期,我们如何创建我们所需要的对象?
- 当我们创建一个新的对象时,如何保证与这个对象所关联的依赖关系(其关联对象)也能够被正确地创建出来呢?
这两大问题不仅是面向对象的编程语言中的核心问题,也是每个框架在进行设计时必须跨越的坎。因而,业界对于这样类的问题也早有公论:
- public interface Container extends Serializable {
- /**
- * 定义默认的对象获取标识
- */
- String DEFAULT_NAME = "default";
- /**
- * 进行对象依赖关系注入的基本操作接口,作为参数的object将被XWork容器进行处理。
- * object内部声明有@Inject的字段和方法,都将被注入受到容器托管的对象,
- * 从而建立起依赖关系。
- */
- void inject(Object object);
- /**
- * 创建一个类的实例并进行对象依赖注入
- */
- <T> T inject(Class<T> implementation);
- /**
- * 根据type和name作为唯一标识,获取容器中的Java类的实例
- */
- <T> T getInstance(Class<T> type, String name);
- /**
- * 根据type和默认的name(default)作为唯一标识,获取容器中的Java类的实例
- */
- <T> T getInstance(Class<T> type);
- /**
- * 根据type获取与这个type所对应的容器中所有注册过的name
- * type
- *
- */
- Set<String> getInstanceNames(Class<?> type);
- /**
- * 设置当前线程的作用范围的策略
- */
- void setScopeStrategy(Scope.Strategy scopeStrategy);
- /**
- * 删除当前线程的作用范围的策略
- */
- void removeScopeStrategy();
- }
- 获取对象实例 —— getInstance、getInstanceName
- 处理对象依赖关系 —— inject
- 处理对象的作用范围策略 —— setScopeStrategy、removeScopeStrategy
既然容器(Container)被定义为一个Java接口,那么我们同时也来关注一下容器的实现类的一些基本特性。
- 容器的初始化需求 —— 我们应该掌握好容器初始化的时机,并考虑如何对容器实例进行系统级别的缓存
- 系统与容器的通讯机制 —— 我们应该提供一种有效的机制与这个全局的容器实例进行沟通
有关这两个不同方面的实现机理,我们将在接下来的章节中陆续给出源码级别的解析。其中有关容器的初始化过程,蕴含在整个框架的初始化主线中,我们将在第九章中详细解读。而系统与容器的通讯机制,则涉及到了XWork容器自身的数据结构和实现机理,因而也成为了本章的重点之一。读者在这里应体会XWork框架在容器的设计上与之前我们所提到的容器设计的基本原则之间的吻合度,这对我们整个面向对象的设计理念将有极大的提升。
5.2.2 XWork容器的管辖范围 既然引入容器(Container)的主要目的在于管理对象的生命周期,那么在明确了XWork的容器定义之后,我们就非常有必要去了解一下XWork容器的管辖范围。换句话说,如果我们拥有了这个全局的容器(Container)实例,当我们调用容器的操作接口时,到底操作的是哪些对象呢? 从容器(Container)操作接口的角度,容器的两类操作接口:获取对象实例(getInstance)和实施依赖注入(inject),它们所操作的对象也有所不同。接下来我们就对这两类不同的操作接口分别进行分析。 5.2.2.1 获取对象实例 当我们调用容器的getInstance方法来获取对象实例时,我们只能够获取到那些“被容器接管”的对象的实例。那么,哪些对象属于“被容器接管”的对象呢? 在第三章中,我们已经介绍过Struts2 / XWork的配置元素以及这些配置元素的分类。当时,我们把XML配置文件中基本节点的分为两类:其中一类是bean节点和constant节点,我们把这两个节点统称为容器配置元素;另外一类则是package节点,这个节点下的所有配置定义都被称之为事件映射关系。而我们进行配置元素分类的基本思路是按照XML节点所表达的逻辑含义和该节点在程序中所起的作用进行的分类。 现在,当我们回过头来再来看配置元素的分类时,我们就能理解“容器配置元素”的真正含义了。在XML配置元素中,bean节点被广泛用于定义框架级别的内置对象和自定义对象;而constant节点和Properties文件中的配置选项,则被用于定义系统级别的运行参数。我们之所以把这两类节点统称为“容器配置元素”,就是因为他们所定义的对象的生命周期,都是由容器(Container)所管理的,这些对象也就是所谓的“被容器接管”的对象。- 在bean节点中声明的框架内部对象
- 在bean节点中声明的自定义对象
- 在constant节点和Properties文件中声明的系统运行参数
在这里需要注意的是,我们通过容器获取到的这些对象的实例,不仅自身被初始化,对象内部的所有依赖对象也已经被正确地实施依赖注入。很显然,这就是我们使用容器(Container)进行对象生命周期管理的好处。
在这里,我们对这三类容器托管对象的归纳,实际上蕴含了我们对自定义对象纳入XWork容器管理的过程:只要在Struts2 / XWork的配置文件中进行声明即可。 5.2.2.2 对象的依赖注入 当我们调用容器的inject方法来实施依赖注入操作时,所操作的对象却不仅仅限于“容器配置元素”中所定义的对象。因为我们对于inject方法的定义是说:只要传入一个对象的实例,容器将负责建立起传入对象实例与容器托管对象之间的依赖关系。 由此可见,虽然传入inject的操作对象是任意的,然而实施依赖注入操作时的那些依赖对象却是被容器(Container)接管的对象。这就为我们为任意对象与XWork容器中所管理的对象之间建立起一条通道提供了有效的途径。- @Target({METHOD, CONSTRUCTOR, FIELD, PARAMETER})
- @Retention(RUNTIME)
- public @interface Inject {
- /**
- * 进行依赖注入的名称。如果不声明,这个名称会被设置为‘default’
- */
- String value() default DEFAULT_NAME;
- /**
- * 是否必须进行依赖注入,仅仅对于方法和参数有效。
- */
- boolean required() default true;
- }
- 为某个对象的方法、构造函数、内部实例变量、方法参数变量加入@Inject的Annotation
- 调用容器(Container)的inject方法,完成被加入Annotation的那些对象的依赖注入
因此,我们在这里顺利解决了我们在容器定义中所提到的一个核心问题:如何建立起系统到容器或者容器托管对象的沟通桥梁 —— 通过@Inject声明来完成。
5.2.3 XWork容器操作详解 5.2.3.1 通过容器(Container)接口进行对象操作 在了解了XWork中的容器的操作定义以及XWork容器的管辖范围之后,我们可以看看如何通过直接操作容器(Container)的实例来进行对象操作。 我们首先来看看如何通过容器(Container)对象来获取对象实例。我们在这里摘取了XWork框架中的一个处理类DefaultUnknownHandlerManager进行说明,其相关源码如代码清单5-3所示:- public class DefaultUnknownHandlerManager implements UnknownHandlerManager {
- protected ArrayList<UnknownHandler> unknownHandlers;
- private Configuration configuration;
- private Container container;
- @Inject
- public void setConfiguration(Configuration configuration) {
- this.configuration = configuration;
- build();
- }
- @Inject
- public void setContainer(Container container) {
- this.container = container;
- build();
- }
- protected void build() {
- // 如果configuration对象不为空,则依次从configuration对象
- // 以及Container中读取UnknowHandler的实例
- if (configuration != null && container != null) {
- List<UnknownHandlerConfig> unkownHandlerStack = configuration.getUnknownHandlerStack();
- unknownHandlers = new ArrayList<UnknownHandler>();
- if (unkownHandlerStack != null && !unkownHandlerStack.isEmpty()) {
- // 根据一定顺序获取UnknownHandlers实例
- for (UnknownHandlerConfig unknownHandlerConfig : unkownHandlerStack) {
- // 调用container对象的getInstance方法获取UnknownHandler
- UnknownHandler uh = container.getInstance(UnknownHandler.class, unknownHandlerConfig.getName());
- unknownHandlers.add(uh);
- }
- } else {
- // 调用container对象的getInstanceNames方法获取
- // 所有受到容器管理的UnknownHanlder实例名称
- Set<String> unknowHandlerNames = container.getInstanceNames(UnknownHandler.class);
- if (unknowHandlerNames != null) {
- // 根据名称调用container对象的getInstance方法获取实例
- for (String unknowHandlerName : unknowHandlerNames) {
- UnknownHandler uh = container.getInstance(UnknownHandler.class, unknowHandlerName);
- unknownHandlers.add(uh);
- }
- }
- }
- }
- }
- // 这里省略了许多其他的代码
- }
- public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable {
- // 这里省略了许多其他的代码
- private TextProvider getTextProvider() {
- if (textProvider == null) {
- TextProviderFactory tpf = new TextProviderFactory();
- if (container != null) {
- container.inject(tpf);
- }
- textProvider = tpf.createInstance(getClass(), this);
- }
- return textProvider;
- }
- @Inject
- public void setContainer(Container container) {
- this.container = container;
- }
- // 这里省略了许多其他的代码
- }
- 通过操作容器进行对象操作的基本前提是当前的操作主体能够获得全局的容器实例。因而,全局的容器实例的获取,在操作主体的初始化过程中完成。
- 通过操作容器进行的对象操作都是运行期(Runtime)操作。
- 通过操作容器所获取的对象实例,都是那些受到容器托管的对象实例。
- 通过操作容器进行的依赖注入操作,可以针对任意对象进行,该操作可以建立起任意对象和容器托管对象之间的联系。
读者在这里或许对这些结论还一知半解,在之后的章节中,我们将一一为读者解开这些容器操作中的疑惑。在这里,读者应首先谨记这四个要点,理解它们的基本要义含义,并且将它们作为XWork容器操作的基本结论。因为我们将在之后的源码分析中经常遇到需要直接操作全局容器实例的范例,牢记这些基本结论之后读者对于Struts2 / XWork中内置对象的操作就不会产生障碍。
5.2.3.2 通过Annotation获取容器对象实例 在展开本节的话题之前,我们首先来回顾一下上一节中我们所得出的一个重要结论:- @Inject
- public void setContainer(Container container) {
- this.container = container;
- }
- public class ObjectProviderTest {
- private ObjectFactory objectFactory;
- @Inject
- public void setObjectFactory(ObjectFactory objectFactory) {
- this.objectFactory = objectFactory;
- }
- }