struts2在初始化的过程中面临一个所有面向对象的编程都需要解决的问题:如何管理在此期间创建的对象,如何处理每个对象之间的关系。
和Spring一样,struts2也采用了控制反转和依赖注入的方式来解决以上的问题。
一、容器的引入
以上的问题需要引入容器,这个容器应该和业务逻辑没有任何关系,且具有以下的特点:
1.容器是一个全局的且唯一的编程元素
2.容器应该最小限度地侵入业务逻辑
3.提供一系列的接口或方法用来获取容器内的对象
二、struts2容器概览
源码中,struts2的容器是Container接口
方法功能概述:
1.获取对象的实例: <T> T getInstance(Class<T> type, String name);
<T> T getInstance(Class<T> type);
2.处理对象的依赖关系: void inject(Object o);
<T> T inject(Class<T> implementation);
3.处理对象的作用范围策略 : void setScopeStrategy(Scope.Strategy scopeStrategy);
void removeScopeStrategy();
容器管理的对象:即容器配置元素,配置文件中的bean节点(框架内置对象和自定义对象)和constant节点、properties文件(系统级别的运行参数)
1、在bean节点中声明的框架内部对象
2、在bean节点中声明的自定义对象(也就是可以以bean的形式扩展框架的功能,并将其交给容器管理)
3、在constant节点和properties中声明的系统运行参数
容器不仅需要管理对象的生成,还要处理对象之间的依赖关系,这是用依赖注入的形式完成的。
容器中的inject方法负责建立当前传入的对象与容器内被管理对象的关联关系,而具体实现的方式则是在需要被注入的位置(属性或者方法)加上@inject标签,最后在容器的inject方法中完成注入。
三、 Xwork的结构详解
两个Map
final Map<Key<?>, InternalFactory<?>> factories 对象的制造工厂,用来在运行期根据对象实例并返回
final Map<Class<?>,Set<String>> factoryNamesByType
在构造函数中,factories作为参数传入,factories中的Key<?>保存了bean节点的对应信息,key中的属性type、name、Class<?>对应了bean配置元素中的相关信息。同时 在构造函数中,根据factores,factoryNamesByType保存了对象类型和对象名字的映射关系。
factories的类型:
Key是一个struts2的自定义类,有三个属性type,name,hashCode。
这些属性和配置文件中的type、name一一对应。
InternalFactory是一个泛型接口,实现create方法会自动生成与key对应的java对象。
注入器
用来记录对象之间的关联关系,也是一个Map
final Map<Class<?>, List<Injector>> injectors =
new ReferenceCache<Class<?>, List<Injector>>() {
@Override
protected List<Injector> create(Class<?> key) {
List<Injector> injectors = new ArrayList<Injector>();
addInjectors(key, injectors);
return injectors;
}
};
该Map的key记录对象类型,value记录与该对象相关联的信息的对象(Injector)
injectors是一个在运行期才进行初始化的map,里面还有一个Map—— ConcurrentHashMap和ThreadLocal类型的变量,二者相互协同解决多线程访问的问题。
当容器初始化时,才用内部类的形式实现create方法并创建注入器Map的injector。在create方法里,会根据不同Class查找满足条件的Injector,并记录关联关系。
具体调用create方法的时机是调用Map Injector的get方法时会首先查找是否存在相应的对象,存在则返回,不存在就调用internalCreate方法创建(这个方法会用到在运行中新创建的实现了抽象方法的新的create方法),多线程问题也在这个创建过程中解决。
创建注入器的过程
所有注入器都是在运行期动态添加的,具体是由create方法调用容器的一个方法 addInjectors(key, injectors)
具体实现如下
List<Injector> injectors = new ArrayList<Injector>();
addInjectors(key, injectors);
return injectors;
这个addinjectors方法逻辑脉络很清晰,具体分为三个步骤
1.首先递归调用自身,完成对父类的注入器的查找
2.根据所有属性查找符合条件的注入器存进injectors的list里 addInjectorsForFields
3.根据所有方法查找符合条件的注入器存进injectors的list里 addInjectorsForMethods
无论查找属性还是方法都会调用addInjectorsForMembers方法,这个方法拥有四个参数
第一个参数代表存储一系列需要注入的位置(属性或者方法)
第二个参数代表是否允许在static的属性或者方法上进行注入
第三个参数使用来存储一系列Injector的list
第四个参数是injector的制造工厂,InjectorFactory ,该参数在调用的时候需要手动创建一个内部类,可以根据注入的位置灵活实现该接口的create方法创建工厂。
该方法实际上是扫描需要注入的位置上是否存在@inject的标签来判断是否注入。
Xwork的实现技巧
一个特殊的方法。模板方法的使用callInContext(ContextualCallable<T>)
容器的实现类中含有一个存储了容器运行期间所需的环境的ThreadLocal对象数组,用来确保线程安全,这个方法就是为容器中其他方法生成一个安全的环境,一旦准备好相关的环境就会调用(ContextualCallable的call方法。容器中其他方法会调用这个模板方法,然后会根据自己的需求实例化该模板方法里的参数(内部类形式),在call方法里使用自己的逻辑。
eg:
public <T> T getInstance(final Class<T> type, final String name) {
return callInContext(new ContextualCallable<T>() {
public T call(InternalContext context) {
return getInstance(type, name, context);
}
});
@SuppressWarnings("unchecked")
<T> T getInstance(Class<T> type, String name, InternalContext context) {
ExternalContext<?> previous = context.getExternalContext();
Key<T> key = Key.newInstance(type, name);
context.setExternalContext(ExternalContext.newInstance(null, key, this));
try {
InternalFactory o = getFactory(key);
if (o != null) {
return getFactory(key).create(context);
} else {
return null;
}
} finally {
context.setExternalContext(previous);
}
}
注入器的具体实现
注入器分为两类,FieldInjector和MethodInjector。由InjectorFactory的create方法创建。
核心方法:inject(inject(InternalContext context, Object o)),其中第一个参数是环境,第二个参数是注入的委托对象。
容器内的inject方法遍历injectors中属于委托对象的注入器集合,然后调用每一个注入器的inject方法完成依赖注入。注意inject也用到了模板方法callInContext。
统一的容器操作接口:ObjectFactory
Container主要用于在配置文件里和预定义的对象的创建,ObjectFactory主要用于在程序的运行期新建对象对容器进行扩展,它提供了一系列的工具方法用于构建Xwork框架内部对象Action Interceptor Result,还有一个是用于构建普通bean的buildBean方法。本质上来说,前面的所有工具构建方法也最终调用了buildBean方法。