XWork容器的定义
XWork框架中的函数被定义成为一个接口,其相关源码如下所示。
Container.java
public interface Container extends Serializable {
/**
* Default dependency name.
* 定义默认的对象获取标识
*/
String DEFAULT_NAME = "default";
/**
* Injects dependencies into the fields and methods of an existing object.
* 进行对象依赖注入的基本接口
* 作为参数的object将被XWork容器进行处理。
* object内部声明有@Inject的字段和方法
* 都将被注入受到容器托管的对象,从而建立依赖关系
*/
void inject(Object o);
/**
* Creates and injects a new instance of type {@code implementation}.
* 创建一个类的实例并进行对象依赖注入
*/
<T> T inject(Class<T> implementation);
/**
* Gets an instance of the given dependency which was declared in
* {@link com.opensymphony.xwork2.inject.ContainerBuilder}.
* 根据type和name作为唯一标识,获取容器中的Java类的实例
*/
<T> T getInstance(Class<T> type, String name);
/**
* Convenience method. Equivalent to {@code getInstance(type,
* DEFAULT_NAME)}.
* 根据type和默认的name(default)作为唯一标识,获取容器中的Java类的实例
*/
<T> T getInstance(Class<T> type);
/**
* Gets a set of all registered names for the given type
* @param type The instance type
* @return A set of registered names or empty set if no instances are registered for that type
* 根据type获取与这个type所对应的的容器中所注册过的name
*/
Set<String> getInstanceNames(Class<?> type);
/**
* Sets the scope strategy for the current thread.
* 设置当前线程的作用范围的策略
*/
void setScopeStrategy(Scope.Strategy scopeStrategy);
/**
* Removes the scope strategy for the current thread.
* 删除当前线程的作用范围的策略
*/
void removeScopeStrategy();
}
从容器的接口定义方法来看,它完全能够符合我们之前所讨论的容器设计的基本原则之一:简单而全面。从接口的内容和表现形式来看,它也能符合我们对容器的基本要求:
容器首先要被设计成一个接口而不是具体的实现类,而整个接口定义中既包含获取对象实例的方法,也包含管理对象依赖关系的方法。
在这里,我们可以看到容器设计的基本原则在一定程度上知道着容器的接口设计,因为我们更加关心容器能够对外提供什么样的服务,而并不是容器自身的数据结构。 从源码中,我么可以依照方法的不同作用对这些操作接口进行分类:
获取对象实例——getInstance、getInstanceName
处理对象依赖关系——inject
处理对象的作用范围策略——setScopeStrategy、removeScopeStrategy
结论 容器是一个辅助的编程元素,它在整个系统中应该被实例化为一个全局的、单例的对象。
这是容器实现中最为基本的一个特性。这也是由容器自身的设计初衷所决定的。如果我们在整个系统中能够获得多个不同的容器的对象实例,或者容器的对象实例在整个系统中的作用域又存在局限性,我、那么我们依托容器进行对象生命周期管理就会变得混乱不堪。
结论 容器在系统初始化时进行自身的初始化。系统应该提供一个可靠的、在任何编程层次都能够对这个全局的容器或者容器中管理的对象进行访问的机制。
这一条结论是我们对容器实现的基本要求。从这条结论中,我们可以看到两个不同的方面:
容器的初始化需求——应该掌握好容器初始化的时机,并考虑如何对容器实例进行系统级别的缓存
系统与容器的通信机制——应该提供一种有效的机制与这个全局的容器实例进行沟通
XWork容器的管辖范围
从容器操作接口的角度来看,容器的两类操作接口:获取对象实例(getInstance)和实施依赖注入(inject),它们所操作的对象有所不同。接下来我们就对这两类不同的操作接口分别进行分析:
获取对象实例
当调用容器的getInstance方法来获取对象实例时,我们只能够获取那些“被容器接管”的对象的实例。而所谓“被容器接管”的对象就是“容器配置元素”,即在XML配置元素中,用于定义框架级别的内置对象和自定义对象的bean节点,和用于定义系统级别的运行参数的constant节点和Properties文件中的配置选项,我们之所以把这两类节点统称为“容器配置元素”,因为他们所定义的对象的生命周期,都是由容器来管理。
结论 XWork容器所管理的对象包括所有框架配置定义中的“容器配置元素”
根据之前的分析,这些对象主要可以分成三类:
在bean节点中声明的框架内部对象
在bean节点中声明的自定义对象
在constant节点和Properties文件中声明的系统运行参数
在这里需要注意的是,我们通过容器获取的这些对象的实例,不仅自身被初始化,对象内部的所有依赖对象也已经被正确地实施依赖注入。很显然,这就是我们使用容器进行对象生命周期管理的好处。 我们在对这三类托管对象的归纳,实际上蕴含了我们对自定义对象纳入XWork容器管理的过程:只要在Struts2/ XWork的配置文件中声明即可。
对象的依赖注入
当调用容器的inject方法来实施依赖注入操作时,所操作的对象却不仅仅限于“容器配置元素”中所定义的对象。 因为我们对inject方法的定义是,只要传入一个对象的实例,容器将负责建立起传入对象实例与容器托管对象之间的依赖关系。
由此可见,虽然传入inject的操作对象是任意的,实施依赖注入操作的那些依赖对象确实被容器接管的对象。这就为任意对象与XWork容器中所管理的对象之间建立起一条通路提供了有效的途径。
结论 调用XWork容器的inject方法,能够帮助我们将容器所管理的对象(包括框架的内置对象以及系统的运行参数)注入到任意的对象实例中去,从而建立起任意对象与框架元素沟通的桥梁。
根据之前对XWork容器对象的定义,我们可以看到inject方法的调用流程:当某个对象实例新作为参数传入方法之后,该方法会扫描传入对象内部声明有@Inject这个Annotation的字段、方法、构造函数、方法参数并将它们注入容器托管对象,从而建立起传入对象与容器托管对象之间的依赖关系。
接下来就来看看@Inject这个Annotation的定义:
Inject.java
@Target({METHOD, CONSTRUCTOR, FIELD, PARAMETER})
@Retention(RUNTIME)
public @interface Inject {
/**
* Dependency name. Defaults to {@link Container#DEFAULT_NAME}.
* 进行依赖注入的名称。如果不声明,这个名称会被设置为‘default’
*/
String value() default DEFAULT_NAME;
/**
* Whether or not injection is required. Applicable only to methods and
* fields (not constructors or parameters).
* 是否必须进行依赖注入,仅仅对于方法和参数有效
*/
boolean required() default true;
}
在这里,@Inject既可以加入到Struts2/ XWork的内置对象之中,也可以加到任意我们自行编写的对象之上。一旦它加入到我们自定义的对象之中,那么就建立起了自定义对象与容器托管对象之间的联系。当我们需要寻求容器帮忙时,只要在恰当的地方加入一个标识符Annotation,容器在进行依赖注入操作时,就能够知晓并接管整个过程了。
在这里,我们看到了两个过程共同构成了XWork容器进行对象依赖注入操作的步骤:
为某个对象的方法、构造函数、内部实例变量、方法参数变量加入@Inject的Annotation
调用容器的Inject方法,完成被加入Annotation的那些变量的依赖注入
因此,我们在这里顺利解决了在容器定义中所提到的一个核心问题:如何建立起系统到容器或者容器托管对象的沟通桥梁——通过@Inject声明来完成
使用容器进行对象操作的几个要点:
通过操作容器进行对象操作的基本前提是当前的操作主体能够获得全局的容器实例。因而,全局的容器实例的获取在操作主体的初始化过程中完成
通过操作容器进行的对象操作都是运行期(Runtime)操作
通过操作容器所获得的对象实例,都是收到容器托管的对象实例
通过操作容器进行的依赖注入操作,可以针对任意对象进行,该操作可以建立起任意对象和容器托管对象之间的联系
参考:整理归纳自《struts2技术内幕——深入解析struts2架构设计与实现原理》