Spring揭秘 — IoC
IoC的基本概念
IoC:Inversion of Control,中文通常翻译为控制反转。它还有个别名叫做依赖注入(Dependency Injection)。
IoC的角色:
通常情况下,被注入对象会直接依赖于被依赖对象。但是,在IoC的场景中,二者之间通过IoC Service Provider来打交道,所有的被注入对象和依赖对象现在由IoC Service Provider统一管理。被注入对象需要什么,直接跟IoC Service Provider招呼一声,后者就会把相应的被依赖对象注入到被注入对象中,从而达到IoC Service Provider为被注入对象服务的目的。IoC Service Provider在这里就是通常的IoC容器所充当的角色。从被注入对象的角度看,与之前直接寻求依赖对象相比,依赖对象的取得方式发生了反转,控制也从被注入对象转到了IoC Service Provider那里。
即:原来需要什么东西自己去拿,现在是需要什么东西就让别人送过来。
注入方式:
1.构造方法注入
构造方法注入就是被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是IoC容器)知道它需要哪些依赖对象。
public class TempClass(AListener aListener, BListener bListener) {
this.aListener = aListener;
this.bListener = bListener;
}
优点: 对象在构造完成后,即已进入就绪状态,可以马上使用
缺点: 当依赖对象比较多时,构造方法参数列表会比较长;通过反射构造对象的时候,对相同类型的参数的处理会比较困难,维护和使用上也比较麻烦;而且在java中,构造方法无法被继承,无法设置默认值。
2.setter方法注入
对于JavaBean对象来说,通常会通过setXXX()和getXXX()方法来访问对应属性。即当前对象只需要为其依赖对象所对应的属性添加setter方法,就可以通过setter方法将相应的依赖对象设置到被注入对象中。
public class TempClass {
private AListener aListener;
private BListener bListener;
public void setAListener(AListener aListener) {
this.aListener = aListener;
}
public void setAListener(BListener bListener) {
this.bListener = bListener;
}
}
优点: 因为方法可以命名,所以setter方法注入在描述性上要比构造方法注入好一些;setter方法可以被继承,允许设置默认值。
缺点: 对象无法在构造完成后立即进入就绪状态
setter方法注入因为入侵性较弱,且易于理解和使用,所以是现在使用最多的注入方式。
IoC的优势:
不会对业务对象构成很强的侵入性;
对象具有更好的可测试性,可重用性和可扩展性;
…
一句话总结,IoC是一种可以帮助我们解耦各业务对象间依赖关系的对象绑定方式!
IoC Service Provider
IoC Service Provider在这里是一个抽象出来的概念,它可以指代任何将IoC场景中的业务对象绑定到一起的实现方式。Spring的IoC容器就是一个提供依赖注入服务的IoC service Provider。
IoC Service provider 的职责
IoC Service provider 的职责主要有两个:业务对象的构建管理和业务对象间的依赖绑定。
业务对象的构建管理: 在IoC场景中,业务对象无需关心所依赖的对象如何构建如何取得。IoC Service provider将对象的构建逻辑从客户端对象那里剥离出来,以免这部分逻辑污染业务对象的实现。
业务对象间的依赖绑定: IoC Service provider通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象注入绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态。
Ioc Service Provider 管理对象间依赖关系的方式
1. 直接编码方式
当前大部分的IoC容器都应该支持直接编码方式,在容器启动前,我们就可以通过程序编码的方式将被注入对象和依赖对象注册到容器中,并明确它们之间的依赖注入关系。
IoContainer container = ...;
container.regiester(FXNewsProvider.class, new FXNewsProvider());
container.regiester(IFXNewsProvider.class, new ANewsListener());
...
FXNewsProvider newsProvider = (FXNewsProvider)container.get(FXNewsProvider.class);
newsProvider.getAndPersistNews();
通过为相应的类指定对应的具体实例,可以告知IoC容器,当我们要这种类型的对象实例时,请将容器中注册的、对应的那个具体实例返回给我们。
2. 配置文件方式
配置文件方式是一种较为普遍的依赖注入关系管理方式。
<bean id="newsProvider" class="...FXNewsProvider">
<property name="newsListener">
<ref bean="aNewsListener"/>
</property>
<property name="newsPersistener">
<ref bean="aNewsPersister"/>
</property>
</bean>
<bean id="aNewsListener" class="...impl.ANewsListener"></bean>
<bean id="aNewsPersister" class="...impl.ANewsPersister"></bean>
然后,我们就可以通过newsProvider这个名字,从容器中取得并使用已经组装好的FXNewsProvider。
...
container.readConfigurationFiles(...);
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("newsProvider");
newwsProvider.getAndPersistNews();
3.元数据方式
这种方式的代表实现是Google Guice,这是Bob Lee在Java5的注解和Generic的基础上开发的一套IoC框架。我们可以直接在类中使用元数据信息来标注各个对象之间的依赖关系,然后由Guice框架根据这些注解所提供的信息将这些对象组装后,交给客户端对象使用。
public class FXNewsProvider{
private IFXNewsListener newsListener;
private IFXNewsPersister newsPersister;
@Inject
public FXNewsProvider(IFXNewsListener newsListener, IFXNewsPersister newsPersister){
this.newsListener = newsListener;
this.newsPersister = newsPersister;
}
...
}
通过@Inject,我们需要IoC Service Provider通过构造方法注入方式,为FXNewsProvider注入其所依赖的对象。至于余下的依赖相关信息,在Guice中是由相应的Module来提供的。
public class NewsBindingModule extends AbstractModule{
@Override
protected void configure(){
bind(IFXNewsListener.class)
.to(ANewsListener.class).in(Scopes.SINGLETON);
bind(IFXNewsPersister.class)
.to(ANewsPersister.class).in(Scopes.SINGLETON);
}
}
通过Module指定进一步的依赖注入相关信息后,我们就可以直接从Guice那里取得最终已经注入完毕,并直接可用的对象了。
Injector injector = Guice.createInjector(new NewsBindingModule());
FXNewsProvider newsProvider = injector.getInstance(FXNewsProvider.class);
newsProvider.getAndPersistNews();
当然,注解最终也要通过代码来确定最终注入的关系,从这点来看,注解方式可以算作编码方式的一种特殊情况。