PICO容器中提供了哪些 依赖注入的方式呢?PICO是怎么 选择的呢?它是 核心架构设计是怎样的呢?
ComponentAdapter设计
ComponentAdapters
可是是PICO中最不容易理解但却非常有强大功用的特性了。它既是一个组件生产工厂(Component Factory),也是一个对象拦截器(Object Intercepter)[责任链模式]
该接口最核心的方法是
T getComponentInstance(PicoContainer container, Type into) throws PicoCompositionException;
事实上,ComponentApdater
唯一的目的就是向PICO
容器提供真正的组件实例。 DefaultPicoContainer
容器并没有维护一个关于class map、instance map或者类似的属性,而是使用了一个以ComponentAdater
为value的map。
每次picoContainer.addComponent()
时,框架背后就会创建一个ComponentAdapter
实例。对于每一个组件component
,都会关联到一个conponentAdater
实例。
PicoContainer.getComponent() 调用时序图
这张图的关键在于定位真正的ComponentAdater
实现类是谁?它正是在执行get组件前,注册组件到容器中时生成的。见方法 MutablePicoContainer .addComponent() or MutablePicoContainer.addAdapter().
Instantiation and Injectors(通过注入器进行实例化组件)
PICO提供了不同的注入方式对组件进行实例化,最出名的肯定是 SetterInjector ,ConstructorInjector。
创建PICO容器时,你也可以指定用固定的注入器去实例化组件。如下就会通过构造器去实例化组件。
MutablePicoContainer pico = new PicoBuilder().withCDI().build();
instance = pico.getComponent(MyObject.class);
Post Instantiation Modification using Behaviors (通过Behavior对组件进行后置更新)
就是组件已经实例化过后,可以通过Behavior
对其进行更新。
再提到ComponetAdaper
,其不仅仅只是负责创建一个组件实例,否则将其称作组件工厂就可以了。它内部还涉及到了责任链模式,
以单例模式为例,
MutablePicoContainer pico = new DefaultPicoContainer(new Caching());
则存在这样的责任链 Cached---->ConstructorInjector。
第一次get component时,最终由ConstructorInjector创建,返回给Cached进行存储。下次get
component时,则直接由Cached返回。
Changing the default ComponentAdapter for a new PicoContainer(更改容器的默认ComponentAdapter)
DefaultPicoContainer
的默认CompoentAdapter
本质上是ConstructorInjector
,即通过构造器去初始化组件。我们可以去改变它,可以从全局上改变(整个PICO容器级别), 也可以是单个组件改变(每次addComponent指定Component Adapter)。
使用PicoBuilder创建容器
// Caching Behaviors
pico = new PicoBuilder().withCaching().build();
// Caching + ImplementationHiding
pico = new PicoBuilder().withCaching().withHiddenImplementations().build();
// Setter Injection
pico = new PicoBuilder().withSDI().build();
使用ComponentFactory(生产ComponentAdapter) 来创建PICO容器
pico = new DefaultPicoContainer(new Caching().wrap(new ConstructorInjection()));
pico = new DefaultPicoContainer(new Caching().wrap(new ImplementationHiding().wrap(new ConstructorInjection)));
pico = new DefaultPicoContainer(new SetterInjection());
在addComponent时,使用指定的Adapter,这里只是给个栗子,不详细展开
Behaviors can be signaled by properties per component:
pico = new DefaultPicoContainer();
pico.as(SOME_BEHAVIOR).addComponent(Foo.class);
// the behavior has a property marking it, and the default component factory understands that property
// other components added
Foo foo = pico.getcomponent(Foo.class)
// Foo instance will be affected by an additional behavior.
至此,我们已经了解到了ComponentAdapter是PICO架构设计的核心,它的两个子接口Injector负责注入初始化组件,Behavior负责组建初始化后的后置行为处理(比如缓存实现单例模式)。以及容器中如何更改使用的ComponentAdapter(容器维度和单次注册组件维度)
Types of Injection
Constructor Injection
通过构造函数来创建一个对象实例是再合适不过的了,也是PICO默认的注入方式。它显示的表明了一个实例需要哪些外部依赖,而且在单元测试时,可以通过mock外部依赖将其注入进去。
1 . 如果组件有多个构造函数,则优先选择参数个数最多的构造器尝试初始化组件实例,如果不满足,则降级到选择参数个数稍少一些的构造器,一直进行下去,直至找到无参构造函数。如果有两个构造函数参数个数相同,且依赖都存在于PICO容器中,则会抛出异常xxx satisfiable constructors is too many xxx
2 . ConstructorInjector
是构造器注入需要的ComponentAdapter, ConstructorInjection
是负责生产ConstructorInjector
的 ComponentFactory。
3 . 看个栗子
// 通过ComponentFactory构造PICO
pico = new DefaultPicoContainer(new ConstructorInjection());
pico.addComponent(Apple.class); // etc Apple apple = pico.getComponent(Apple.class);
// Constructor Injection, is a default too (via AdaptiveInjection):
pico = new DefaultPicoContainer();
pico.addComponent(Apple.class); // etc
Apple apple = pico.getComponent(Apple.class);
Setter Injection
nothing, just see a example
// Apple类中有setBanana,setPear, setOrange方法
pico = new DefaultPicoContainer(new SetterInjection());
pico.addComponent(Apple.class);
pico.addComponent(Banana.class);
pico.addComponent(Pear.class);
pico.addComponent(Orange.class);
// etc
Apple apple = pico.getComponent(Apple.class);
// 不使用setter方法,使用自定义的方法mySynonymForSet代替setter方法
pico = new DefaultPicoContainer(newSetterInjection("mySynonymForSet"));
pico.addComponent(Apple.class); // etc
Apple apple = pico.getComponent(Apple.class)
Annotated Field Injection
1 . see a example
public class Apple {
@Inject private Orange orange;
@Inject private Pear pear;
@Inject private Banana banana;
// methods
}
Usage:
pico = new DefaultPicoContainer(new AnnotatedFieldInjection();
pico.addComponent(Apple.class);
// etc
Apple apple = pico.getComponent(Apple.class);
// With an custom annotation instead of PicoContainer’s @Inject
// 使用自定义注解
pico = new DefaultPicoContainer(new AnnotatedFieldInjection(MyInjectAnnotaton.class);
pico.addComponent(Apple.class);
// etc
Apple apple = pico.getComponent(Apple.class);
2 . 构造函数注入完成生成组件实例之后,主类中如果有@Inject
注解属性,则会在容器中找到合适的类进行注入。
Annotated Method Injection
基本同上,差别在于一个注解在属性上,一个在方法上。
Typed Field Injection
按类型进行字段注入。
1 .see a example
import static org.picocontainer.injectors.TypedFieldInjection.injectionFieldTypes;
pico = new DefaultPicoContainer(new TypedFieldInjection();
pico.as(injectionFieldTypes(Orange.class, Pear.class, Banana.class)).addComponent(Apple.class); // etc
Apple apple = pico.getComponent(Apple.class);
Apple
组件通过无参构造函数初始化,三个属性Orange, Pear, Banana将会从容器中找到合适的组件进行注入。TypedFieldInjection
是其使用的ComponentFactory
。
Named Field Injection
按名称进行字段注入。
import static org.picocontainer.injectors.NamedFieldInjection.injectionFieldNames;
pico = new DefaultPicoContainer(new NamedFieldInjection();
pico.as(injectionFieldNames("orange", "pear", "banana")).addComponent(FruitBasket.class);
// etc
FruitBasket fruitBasket = pico.getComponent(FruitBasket.class);
同上,FruitBasket
组件通过无参构造函数初始化,三个属性orange, pear, banana将会从容器中找到合适的组件进行注入。NamedFieldInjection
是其使用的ComponentFactory
。
Method Injection
方法注入。
// Apple初始化后, 执行Apple的inject方法
pico = new DefaultPicoContainer(new MethodInjection();
pico.addComponent(Apple.class); // etc
Apple apple = pico.getComponent(Apple.class);
// Apple初始化后,执行自定义的注入方法mySynonymForInject
pico = new DefaultPicoContainer(new MethodInjection("mySynonymForInject");
pico.addComponent(Apple.class); // etc
Apple apple = pico.getComponent(Apple.class);
// 通过特性
pico = new DefaultPicoContainer();
pico.as(Characteristics.METHOD_INJECTION).addComponent(Apple.class);
// etc
Apple apple = pico.getComponent(Apple.class);
Named Method Injection
按指定名称方法注入。
解释: 当Winter存在方法setX(Y y)
时,会尝试到容器中找到名称为X
(不区分大小写)的依赖组件注入进去。如果不存在名称为X
的组件,则尝试方法入参类型组件匹配,寻找类型为Y的依赖组件进行注入,如果有多个类型为Y
的组件,则会抛出 ... but there are too many choices to inject. ...
异常。
class Winter{
public void setX(Snow snow){};
public void setColdWind(ColdWind coldWind){};
}
class ColdWind{}
class Snow{}
class HeavySnow extends Snow{};
// 测试
main{
MutablePicoContainer pico = new DefaultPicoContainer(new NamedMethodInjection());
pico.addConfig("a", new Snow());
pico.addConfig("b", new ColdWind());
pico.addConfig("c", new HeaveySnow());
pico.addComponent(Winter.class);
// winter的setX方法为setA方法,注入Snow
// winter的setX方法为setC方法,注入HeavySnow
// X不是A,C时,按方法入参需要注入一个Snow组件,但容器中有两个符合要求,抛出异常。
}
Multiple Injection
多方式支持注入。
public class Apple {
private Orange orange;
private Pear pear;
// Annotated Field注入
@Inject private Banana banana;
// Constructor Inject 构造器注入
public Apple(Orange orange) {
this.orange = orange;
}
// Method Injection 注入
public void inject(Pear pear) {
this.pear = pear;
}
// other methods
}
Composite Injection
与Multiple Injection
类似,但是可以自定义注入方式的组合。
public class Apple {
private Orange orange;
private Pear pear;
@FruitNeeded private Banana banana;
public Apple(Orange orange) {
this.orange = orange;
}
public void fruitNeeded(Pear pear) {
this.pear = pear;
}
// other methods
}
// Usage
pico = new DefaultPicoContainer(
new CompositeInjection(
new ConstructorInjection(),
new AnnotatedFieldInjection(FruitNeeded.class),
new MethodInjection("fruitNeeded")));
pico.addComponent(Apple.class);
// etc
Apple apple = pico.getComponent(Apple.class);
Factory Injector
看个例子
public class Apple {
private final Log log;
private final Orange orange;
private final Pear pear;
private final Banana banana;
public Apple(Orange orange, Pear pear, Banana banana, Log log) {
this.orange = orange; this.pear = pear; this.banana = banana; this.log = log;
}
// methods
}
通过Factory Injector
注入Log
public class LogInjector extends FactoryInjector<Log>{
public Log getComponentInstance(PicoContainer container, final Type into) throws PicoCompositionException {
return LogFactory.getLog((Class) into);
}
}
...
pico = new DefaultPicoContainer(new ConstructorInjection());
pico.addComponent(Apple.class);
pico.addAdapter(new LogInjector());
// etc Apple apple = pico.getComponent(Apple.class);
Reinjection
重注入,用在组件实例生成之后,执行组件的某个后置处理方法。
public class ShoppingCart {
private Store store;
private User user;
public ShoppingCart(Store store, User user) {
this.store = store;
this.user = user;
}
public boolean addItemTo(Make make, Model model, int quantity) {
Cart cart = store.getCart(user);
if (cart.contains(make, model)) {
cart.on(make, model).increaseQuantity(quantity);
} else {
cart.addItem(make, model, quantity);
}
return true;
}
}
使用方式
MutablePicoContainer pico = new TransientPicoContainer();
pico.addComponent(ShoppingCart.class, myShoppingCartInstance);
pico.addComponent(Make.class, myMake); // you are more likely to use providers that directly hard code values like this.
pico.addComponent(Model.class, myModel);
pico.addComponent(int.class, myQuantity);
// 会执行addItem方法
boolean result = (Boolean) new Reinjector(pico).reinject(ShoppingCart.class, "addItemTo");
Injection via Providers
通过Provider
特性执行注入。
public class Chocolatier implements Provider {
public Chocolate provide(CocaoBeans cocaoBeans) {
return new Chocolate(cocaoBeans);
}
}
// 通过巧克力豆生产巧克力
pico.addAdapter(new ProviderAdapter(new Chocolatier()));
// 依赖巧克力的生成
pico.addComponent(NeedsChocolate.class);
Usage:
pico.addAdapter(new ProviderAdapter(new Chocolatier()));
pico.addComponent(NeedsChocolate.class);
pico.addComponent(CocaoBeans.class);