通过struts2源码分析-IOC容器的实现机制(上篇),可以从中了解了很多关于struts2-IOC容器初始化的东西,如果容器托管对象是什么,
<bean>
节点中为什么有了
type
属性还要有
name
属性,
ContainerBuilder
构建
Container
原理等。主要是讲解了IOC容器的初始化过程,而对从容器中获取容器托管对象以及注入原理一笔带过了。该篇文章将详细讲解如何从容器中获取容器托管对象以及注入原理。
讲解之前有些结论是必须要知道的:
-
什么是容器托管对象?
按struts2/XWork的配置元素节点可分为两类,一类是
<bean>
节点和
<constant>
节点,这两个节点为“容器配置元素”,另一类是
package
节点,这个节点下的所有配置定义为"事件映射关系"。其中
<bean>
节点广泛用于定义框架级别的内置对象和自定义对象,而constant节点和Properties文件中的配置选项则被用于定义系统级别的运行参数。
<bean>
节点,
<constant>
节点加上Propeties文件中的配置选项统称为"容器配置元素",就是因为它们所定义的对象的生命周期都是由容器管理的,所以这些对象就是容器托管对象。
-
Container
中的容器托管对象并不是直接存储在容器中的,实际上容器中存储的是对象的工厂,有了对象工厂就可以随时在运行期 获得对象的实例,存储工厂有个好处就是可以控制对象的产生,例如是要返回一个新的对象呢,还是返回一个单例对象,或者说返 回一个线程安全的对象等。
一、依赖注入
依赖注入也就是调用
Container
中的
inject
方法,先看一下该方法声明
void inject(Object o);<T> T inject(Class<T> implementation);
方法有两个,第一个是传入一个
Object
,对该对象进行注入,注意,这里的对象可以是任意对象,不一定要是容器托管对象。第二个方法传入一个
Class
对象,完成注入并返回一个注入好了的对象实例。
下面是
@Injector
注解的源码:
@Target({METHOD, CONSTRUCTOR, FIELD, PARAMETER})@Retention(RUNTIME)public @interface Inject {String value() default DEFAULT_NAME;boolean required() default true;}
从其声明可以看到,该注释可标于方法,构造器,字段,参数列表中,这也对应了进行注入时可能用到的各种类型的注入器。其
value
属性为查找
InternalFactory
(容器托管对象的工厂)的
name
属性值,默认值为
default
。
Container
为一个接口,其实现类为
ContainerImpl
,当我们要进行注入时,其实调用的就是
ContainImpl
类的inject方法。如下
ContainerImpl
中
void inject(Object o)
的实现:
public void inject(final Object o) {callInContext(new ContextualCallable<Void>() {public Void call(InternalContext context) {inject(o, context);return null;}});}
在该实现方法当中调用的是
callInContext
方法,这是一个模版方法,在后面可以看到,从容器中获取对象的方法
getInstance
内部调用的也是该方法,其目的是将所有接口操作进行规范化定义,作为一个统一操作的入口,并将它们纳入一个线程安全的上下文执行执行环境中。而具体的执行逻辑则被封装于
ContextualCallable
接口的回调实现之内。对于不同的接口实现,它们会拥有不同的逻辑,而这些逻辑则由
ContextualCallable
接口实现类来完成,正因为这样才有可能
inject
方法与
getInstance
方法内部调用的都是同一个
callInContext
方法。这就是模版方法的使用,模版方法只是规定了程序的执行流程,而程序执行的具体逻辑可以由各调用者自己指定。这里
public Void call(InternalContext context)
即是那个回调方法。
如下是
callInContext
方法的源码:
<T> T callInContext(ContextualCallable<T> callable) {Object[] reference = localContext.get();if (reference[0] == null) {reference[0] = new InternalContext(this);try {return callable.call((InternalContext)reference[0]);} finally {// Only remove the context if this call created it.reference[0] = null;}} else {// Someone else will clean up this context.return callable.call((InternalContext)reference[0]);}}
在该方法当中就是调用了前面注册的回调方法
call
,其余的操作都是在获取一个正确的执行上下文。在回调方法
call
中调用了
void inject(Object o, InternalContext context)
方法,我们进行该方法的内部看看:
void inject(Object o, InternalContext context) {List<Injector> injectors = this.injectors.get(o.getClass());for (Injector injector : injectors) {injector.inject(context, o);}}
该方法其中的逻辑很简单,首先获取该对象所需要的注入器(
Injector
),再循环迭代第一个注入器,调用注入器的
inject
方法完成注入。
这里需要讲的是这句
List<Injector> injectors = this.injectors.get(o.getClass());
这是从一个缓存对象(
Map
)中获取所需注入器的操作,这是
ContainerImpl
中
injectors
的声明:
final Map<Class<?>, List<Injector>> injectors =new ReferenceCache<Class<?>, List<Injector>>() {@Overrideprotected List<Injector> create(Class<?> key) {List<Injector> injectors = new ArrayList<Injector>();addInjectors(key, injectors);return injectors;}};
其实大家可以看到,其实该对象就是个
Map
,只不过增加了缓存的功能,其
key
为一
Class
对象,就是被注入对象的
Class
对象,其value为该对象注入所需注入器,当第一次为某一对象进行注入时,获取的注入器肯定为
null
,这时就会去查找该对象注入所需要的注入器。
大家可以看到获取注入器用的是
ReferenceCache
的
get
方法,这是
get
方法的源代码:
@SuppressWarnings(\"unchecked\")@Override public V get(final Object key) {V value = super.get(key);return (value == null)? internalCreate((K) key): value;}
如果说没有找到指定
key
的值,该方法则会创建一个新的值放入
Map
中并返回,这个新的值其实就是注入器列表。
这是
internalCreate
方法中的两句源码:
FutureTask<V> futureTask = new FutureTask<V>(new CallableCreate(key));//中间省略...futureTask.run();
这里新建了一个
CallableCreate
对象封装到了
futureTask
对象中,后面又调用了
futureTask.run();
在
run
方法内容调用的又是一个
Sync
对象的
innerRun()
方法,在
innerRun
方法中调用了
CallableCreate
对象的
call
方法,而该
call
方法中调用的正是
injectors
声明中注册的
create(key)
回调方法。
好了,现在可以将注意力集中到该
create(key)
方法上了来,该中调用了
addInjectors(key, injectors);
方法,该方法源码为:
void addInjectors(Class clazz, List<Injector> injectors) {if (clazz == Object.class) {return;}// Add injectors for superclass first.addInjectors(clazz.getSuperclass(), injectors);// TODO (crazybob): Filter out overridden members.addInjectorsForFields(clazz.getDeclaredFields(), false, injectors);addInjectorsForMethods(clazz.getDeclaredMethods(), false, injectors);}
该方法是一个递归方法,首先查找父类注入器,先为父类实施注入,其次是自己。这里
addInjectorsForFields
和
addInjectorsForMethods
调用的都是一个名为
addInjectorsForMembers
的方法,下面是
addInjectorsForMembers
源码:
<M extends Member & AnnotatedElement> void addInjectorsForMembers(List<M> members, boolean statics, List<Injector> injectors,InjectorFactory<M> injectorFactory) {for (M member : members) {if (isStatic(member) == statics) {Inject inject = member.getAnnotation(Inject.class);if (inject != null) {try {injectors.add(injectorFactory.create(this, member, inject.value()));} catch (MissingDependencyException e) {if (inject.required()) {throw new DependencyException(e);}}}}}}
该方法只是调用
InjectorFactory
的
create
方法返回一个
Injector
并放入到注入器列表当中,
create
方法呢又是一个回调方法,这里以
FieldInjector
为例,这是
addInjectorsForFields
方法的源码:
void addInjectorsForFields(Field[] fields, boolean statics,List<Injector> injectors) {addInjectorsForMembers(Arrays.asList(fields), statics, injectors,new InjectorFactory<Field>() {public Injector create(ContainerImpl container, Field field,String name) throws MissingDependencyException {return new FieldInjector(container, field, name);}});}
create
方法中传入容器对象,
Field
,
name
返回了一个字段注入器,方法注入器原理也是如些,这里就不说了。
在
FieldInjector
构造方法内问,根据
field
类型与
name
参数作为
key
在容器中查找对应容器托管对象的
InternalFactory
,下面是
FieldInjector
类
inject
方法的实现:
public void inject(InternalContext context, Object o) {ExternalContext<?> previous = context.getExternalContext();context.setExternalContext(externalContext);try {field.set(o, factory.create(context));} catch (IllegalAccessException e) {throw new AssertionError(e);} finally {context.setExternalContext(previous);}}
大家可以看到最终调用的就是通过反射技术调用
field.set()
方法,要设置的值就是相应的
InternalFactory
的
create
方法返回的值。到这里大家对
struts2
中的注入原理应该有一定的了解了吧。
二、获取容器托管对象
这是在
Container
接口中的声明:
<T> T getInstance(Class<T> type, String name);<T> T getInstance(Class<T> type);
其实这两个方法的实质是一样的,第二个其实就是一个简单方法,相当于
getInstance(type,DEFAULT_NAME);
就是以type和name作为联合主键进行查找。
再看看
ContainerImpl
的具体实现:
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);}});}
这里调用的方法如同上面所说,都是
callInContext
这个模版方法,其真正逻辑实现是
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);}}
该方法代码也很简单,首先根据
type
,
name
构造一个
Key
对象,其实
Key
就是一个用于存储
type
与
name
的一个POJO,用它作为
key
去容器中查找相应的
InternalFactory
对象,然后调用
InternalFactory
对象的
create
方法返回该值。
这里大家可能会问:struts2中bean不是可以声明变量范围吗,如singleton,request,session,thread的吗,这里只是直接调用了
InternalFactory
的
create
方法,这样能达到目的吗,前面也已经说了容器中存储这些工厂其好处就是可以控制对象的产生,这些功能逻辑都已经封装在
InternalFactory
当中的,具体的请参看struts2源码分析-IOC容器的实现机制(上篇),里面有很详细的说明。
struts2中呢大量应用了模版方法,回调方法,刚看可能会不习惯,多看几遍应该没有问题的。希望对大家有所帮助。
——————————– END ——————————-
及时获取更多精彩文章,请关注公众号《Java精讲》。