一、简介
感谢“简易java框架”分享的学习心得。循着他的足迹,我把picocontainer读了一遍。源代码的版本是1.2-RC-2。
pico的官方站点:http://www.picocontainer.org/
由于它是一个专门的ioc容器,所以使用起来没有spring那么麻烦。关于他的文档,在官方站点上有一篇《5分钟搞定pico》的文章。国人似乎也有很多的翻译版本。讲解得很详细,大家可以看看。
二、快速入手
先来体验一下pico的最简单的用法。
public
static
void
main(String[] args)
{
MutablePicoContainer pico
=
new
DefaultPicoContainer(
new
SetterInjectionComponentAdapterFactory());
pico.registerComponentImplementation(User.
class
,User.
class
,
new
Parameter[]
{
new
ConstantParameter(
new
String
(
"
lvhb
"
)),
new
ConstantParameter(
new
String(
"
boy
"
))}
);
pico.registerComponentInstance(String.
class
,
"
namea
"
);
pico.registerComponentImplementation(LifeUser.
class
);
//
new VerifyingVisitor().traverse(pico);
//
pico.start();
LifeUser user
=
(LifeUser)pico.getComponentInstance(LifeUser.
class
);
user.start();
}
在LifeUser的start方法中,我这样写到:
public void start() {
// TODO Auto-generated method stub
System.out.println("lifeuser start");
System.out.println(user.getName());
}
我们构造了2个类,一个是User类,一个是LifeUser类。由于pico有管理生命周期的功能,我们把LifeUser继承自Startable.
User类有个2个属性.name和sex.
LifeUser有3个属性.name,age和User,这里安排一个User是为了观察它的依赖注入的过程.
三、结构分析
下图是PicoContainer的体系结构。
外部需要的api都有MutablePicoContainer提供.他提供了注册/取消实现,注册/取消实例和设置父子关系的操作.
在DefaultPicoContainer中的componentKeyToAdapterCache属性用来存储注册过的各种类或者实例.
下图是ComponentAdapter的体系结构
ComponentAdapter是pico功能实现的主体.
我们知道在DefaultPicoContainer我们是用一个hashmap存储的key->value的键值对。
其中的key就是我们注册的接口,如果没有提供接口,容器就用这个实现类作key.比如在上面的例子中,
pico.registerComponentImplementation(LifeUser.class);我们也可以要LifeUser继承自ILifeUser,然后写成这样
pico.registerComponentImplementation(ILifeUser.class,LifeUser.class);
value就是我们要介绍的ComponentAdapter.
在ComponentAdapter接口中,提供了5个方法,分别是:
getComponentKey()获得自己在PicoContainer里面的key
getComponentImplementation()获得自己的实现类
getComponentInstance(PicoContainer container)生成自己的一个实例
verify(PicoContainer container)检验这个ComponentAdapter的依赖性是否完整。
accept(PicoVisitor visitor)
ComponentAdapter接口的儿孙很多,但是我们知道,流行的依赖注入目前有2中形式:构造注入和设值注入。
因此尽管实现或者继承ComponentAdapter的各种类很多,最后用到的必将是2个类。
SetterInjectionComponentAdapter和ConstructorInjectionComponentAdapter。有哪些中间的ComponentAdapter实现呢?
来看看ComponentAdapter得层次:
首先有个抽象类MonitoringComponentAdapter继承他,在目前的版本中,monitor功能并没有真正实现。
抽象类AbstractComponentAdapter继承自MonitoringComponentAdapter。在他的构造函数里完成对componentKey和componentImplementation的保存。 并对componentImplementation和componentKey是否兼容作判断。
抽象类InstantiatingComponentAdapter继承自AbstractComponentAdapter。他检查传入的componentImplementation是否能够被实例化,并保存了注册时传入的参数,是否允许没有public构造方法,以及生命周期策略。值得注意的是 这里声明了一个抽象类Guard,用来在实现不同的注入方式时作回调函数。
最终的SetterInjectionComponentAdapter和ConstructorInjectionComponentAdapter都继承自InstantiatingComponentAdapter
在ComponentAdapter的儿孙中有个CachingComponentAdapter,他继承自DecoratingComponentAdapter,实现了LifecycleManager。 顾名思义,他实现了我们的cache.即提供单实例模式。并提供了生命周期管理的功能。
这里使用了修饰模式。CachingComponentAdapter是SetterInjectionComponentAdapter和ConstructorInjectionComponentAdapter的修饰类。
在这个类的内部,额外实现了单实例。并对生命周期相关的过程作校验处理。
他实现单实例的方法比较有意思.
if (instanceReference.get() == null) {
Object instance = super.getComponentInstance(container);
instanceReference.set(instance);
}
四、调试跟踪--注册。
ok,看看运行的流程。
我们先来测试默认的用法:
public
static
void
main(String[] args)
{
MutablePicoContainer pico
=
new
DefaultPicoContainer();
pico.registerComponentImplementation(User.
class
,User.
class
,
new
Parameter[]
{
new
ConstantParameter(
new
String
(
"
lvhb
"
)),
new
ConstantParameter(
new
String(
"
boy
"
))}
);
pico.registerComponentInstance(String.
class
,
"
namea
"
);
pico.registerComponentImplementation(LifeUser.
class
);
//
new VerifyingVisitor().traverse(pico);
pico.start();
LifeUser user
=
(LifeUser)pico.getComponentInstance(LifeUser.
class
);
//
user.start();
}
这段代码将输出start里面的内容.
lifeuser start
lvhb
----
让我们来debug一下上面提到的测试例子.
1.调用DefaultPicoContainer(ComponentAdapterFactory componentAdapterFactory,LifecycleStrategy
lifecycleStrategyForInstanceRegistrations,PicoContainer parent)
parent可以为null,其他2个如果没有设置,将用默认的DefaultComponentAdapterFactory和new DefaultLifecycleStrategy(new DefaultComponentMonitor()).
2.注册User类.调用registerComponentImplementation,并传递2各参数.先调用pico初始化设置的componentAdapterFactory生成componentAdapter
ComponentAdapter componentAdapter =componentAdapterFactory.createComponentAdapter(componentKey, componentImplementation, parameters).
我们进入createComponentAdapter的方法体:
return new CachingComponentAdapter(new ConstructorInjectionComponentAdapter(componentKey, componentImplementation, parameters, false, currentMonitor(), lifecycleStrategy));
这里的monitor和lifecycleStrategy在容器初始化componentAdapterFactory的时候已经设置.我们在下面的内容将忽略关于监视器和生命周期管理的内容.
这里用了ConstructorInjectionComponentAdapter,并交给CachingComponentAdapter修饰.
在里面的内容上面已经介绍了,ConstructorInjectionComponentAdapter一级一级的向上传递参数,一层扒一层皮.完成各种检查.
在包装后的CachingComponentAdapter上调用registerComponent(componentAdapter),即把它加到pico的hashmap中.
3.相同的原理完成String和LifeUser的注册.
五.调试跟踪--获得实例
1.获得adapter.
调用pico的getComponentInstance(Object componentKey)方法.下面是DefaultPicoContainer的方法体:
public
Object getComponentInstance(Object componentKey)
{
ComponentAdapter componentAdapter
=
getComponentAdapter(componentKey);
if
(componentAdapter
!=
null
)
{
return
getInstance(componentAdapter);
}
else
{
return
null
;
}
}
getComponentAdapter(componentKey)将会在pico的hashmap中查找对应的componentKey,如果找不到,在父容器里面找.
获得componentAdapter后,调用getInstance(componentAdapter);方法.因为有可能是在父类中找到的adapter.所以做了一定的判断.
2.通过adapter产生实例
最终调用instance = componentAdapter.getComponentInstance(this);方法.
由于ComponentAdapter的实现是个ConstructorInjectionComponentAdapter,我们来看他的这个方法.
这里有最重要的2个方法,内部类Guard的run()和ComponentAdapter的getGreediestSatisfiableConstructor(PicoContainer container).
进入getComponentInstance后,首先构造了一个抽象类Guard的匿名内部类给instantiationGuard,在这个类的run方法里面:
a:调用getGreediestSatisfiableConstructor方法获得最佳的构造函数.
b:调用getConstructorArguments获得所需的参数
c:调用newInstance(constructor, parameters)生成实例.
我们把刚才的instantiationGuard赋上所需的参数,然后调用他的observe方法.该方法:
public final Object observe(Class stackFrame) {
if (Boolean.TRUE.equals(get())) {
throw new CyclicDependencyException(stackFrame);
}
Object result = null;
try {
set(Boolean.TRUE);
result = run();
} catch (final CyclicDependencyException e) {
e.push(stackFrame);
throw e;
} finally {
set(Boolean.FALSE);
}
return result;
}
调用了我们设置的run()并返回结果.
六.获得构造函数并传递参数的具体过程
这里要说的即上面获得实例过程中run方法的实现细节.
1.调用getGreediestSatisfiableConstructor方法获得最佳的构造函数.
a.首先调用getSortedMatchingConstructors方法初步筛选构造函数.
for
(
int
i
=
0
; i
<
allConstructors.length; i
++
)
{
Constructor constructor
=
allConstructors[i];
if
((parameters
==
null
||
constructor.getParameterTypes().length
==
parameters.length)
&&
(allowNonPublicClasses
||
(constructor.getModifiers()
&
Modifier.PUBLIC)
!=
0
))
{
matchingConstructors.add(constructor);
}
}
上面这段意思是说如果注册时没有提供参数,把所有构造函数列为候选,如果有提供参数,选取和提供参数个数相同的构造函数作为候选.排除私有构造函数.用一个ArrayList保存所有的候选构造函数.如果注册时候没有配参数,那么按构造参数从多到少排列.存放在sortedMatchingConstructors中.
b.然后遍历每一个构造函数,检查是否是我们所需.
我们来看这几行代码:
Class[] parameterTypes
=
constructor.getParameterTypes();
Parameter[] currentParameters
=
parameters
!=
null
?
parameters : createDefaultParameters(parameterTypes);
//
remember: all constructors with less arguments than the given parameters are filtered out already
for
(
int
j
=
0
; j
<
currentParameters.length; j
++
)
{
//
check wether this constructor is statisfiable
if
(currentParameters[j].isResolvable(container,
this
, parameterTypes[j]))
{
continue
;
}
unsatisfiableDependencyTypes.add(Arrays.asList(parameterTypes));
unsatisfiedDependencyType
=
parameterTypes[j];
failedDependency
=
true
;
break
;
}
c.检查的关键是这段currentParameters[j].isResolvable(container, this, parameterTypes[j])
说到这里,我们先看看他的Parameter体系
parameters是我们在注册过程中构造componentAdapter时保存的.(User.class有参数,String和LifeUser都没有).
从UML图上看到,parameter有3种,我们这里着重介绍ConstantParameter和ComponentParameter
ConstantParameter用来包装常量性质的参数,比如本文提供的例子中的参数.
来看他isResolvable方法:
这个方法应该是检查注册时的每个参数和构造函数的每个参数是否匹配,我们进去看看.
他的!checkPrimitive(expectedType) && !expectedType.isInstance(value)表达式前半句对基本类型作了处理.后半句判断注册的参数的值是否是构造函数对应参数类型兼容.
ComponentParameter用来包装组件(自定义类)类型的参数.他的isResolvable和ConstantParameter是不同的.
下面略去500字.(getTargetAdapter, List found = container.getComponentAdaptersOfType(expectedType);)
d.如果提供的注册参数都是构造函数的依赖.那么failedDependency=false.我们把这个构造函数先保存下来
greediestConstructor = constructor;
lastSatisfiableConstructorSize = parameterTypes.length;
接着去检查下一个构造函数是否所需.
显然,如果注册时设置了参数,那么parameterTypes就会是个固定值,因为在上面的筛选中都是选择的相同的参数个数的构造方法.如果出现另外一个可以匹配的构造函数,而且参数个数相同的情况,说明存在冲突.
我们来考虑parameters==null的情况.这个时候返回的是所有的构造函数.而且它的currentParameters =createDefaultParameters(parameterTypes).
createDefaultParameters方法是InstantiatingComponentAdapter实现的.代码如下:
protected
Parameter[] createDefaultParameters(Class[] parameters)
{
Parameter[] componentParameters
=
new
Parameter[parameters.length];
for
(
int
i
=
0
; i
<
parameters.length; i
++
)
{
componentParameters[i]
=
ComponentParameter.DEFAULT;
}
return
componentParameters;
}
显然,他默认设置了ComponentParameter作为参数.每个参数都是一个new ComponentParameter().参照c节介绍的判断方法.
如果构造函数的每个参数都能找到依赖,(因为是参数从大到小排列),那么它就应该是最合适的构造函数.
e.ok,我们找到了最好的构造函数了.
现在需要给这个函数找到各个参数的值.
Parameter[] currentParameters = parameters != null ? parameters : createDefaultParameters(parameterTypes);
for (int i = 0; i < currentParameters.length; i++) {
result[i] = currentParameters[i].resolveInstance(container, this, parameterTypes[i]);
}
ConstantParameter的resolveInstance方法很简单,就是返回parameters上对应的值.
ComponentParameter稍显复杂,和isResolvable类似,它需要在容器里面找到自己想要得值.先找到构造函数参数类型的ComponentAdapter,
然后根据adapter返回实例.在他的resolveInstance方法中这样写到:return container.getComponentInstance
(componentAdapter.getComponentKey()),显然如果有多层级联,会逐层实例下去.
我们把找到的参数的值组成一个Object的数组.
f.生成实例
把构造函数和参数发送给Object inst = newInstance(constructor, parameters);由newInstance返回一个实例.newInstance方法很简单,调用jdk的方法:constructor.newInstance(parameters);
g.大功告成.
七.这里举的是构造注入的方式.设置注入在生成实例的部分和构造注入有些区别,大体类似.
八.来看看与生命周期相关的内容.
有生命周期的组件都能在pico注册完成后通过调用start()方法用iterator模式逐个启动--即调用每个组件的start()方法.
容器先启动自己的所有有关的adapter, 然后去启动所有子类的有关adapter.
启动的命令是: this.lifecycleManager.start(this);
在DefaultPicoContainer里面,有个内部类,实现了LifecycleManager接口.
private LifecycleManager lifecycleManager = new OrderedComponentAdapterLifecycleManager();
我们来看OrderedComponentAdapterLifecycleManager的start()方法的实现.
首先筛选有生命周期的adapter
for
(
final
Iterator iter
=
adapters.iterator(); iter.hasNext();)
{
final
ComponentAdapter adapter
=
(ComponentAdapter)iter.next();
if
( adapter
instanceof
LifecycleManager )
{
LifecycleManager manager
=
(LifecycleManager)adapter;
if
(manager.hasLifecycle())
{
//
create an instance, it will be added to the ordered CA list
adapter.getComponentInstance(node);
addOrderedComponentAdapter(adapter);
}
}
}
上面的意思是说.adapter必须实现LifecycleManager接口(通过UML图可以看到,只有有限的几个实现了他)而且实现类必须实现了startable.
参照CachingComponentAdapter构造函数中下面的片断
this.delegateHasLifecylce = delegate instanceof LifecycleStrategy
&& ((LifecycleStrategy) delegate).hasLifecycle(delegate.getComponentImplementation());
然后对筛选出来的adapter逐个启动
for
(
final
Iterator iter
=
adapters.iterator(); iter.hasNext();)
{
final
Object adapter
=
iter.next();
if
( adapter
instanceof
LifecycleManager )
{
LifecycleManager manager
=
(LifecycleManager)adapter;
manager.start(node);
startedComponentAdapters.add(adapter);
}
}
stop()和dispose()方法则直接利用start()筛选好的adapter.
----end----
参考文档:
http://dl.easyjf.com/downloads/stef_wu-PicoContainer-code.doc
7/23/2006