跳到主要内容

IoC注入方式1

本文相关代码(来自官方源码spring-test模块)请参见spring-framework org.springframework.mylearntest包下。

构造方法注入

public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister) {
this.newsListener = newsListner;
this.newPersistener = newsPersister;
}
  • 优点:对象在构造完成之后,即已进入就绪状态,可以马上使用。
  • 缺点: 当依赖对象比较多的时候,构造方法的参数列表会比较长。而通过反射构造对象的时候,对相同类型的参数的处理会比较困难,维护和使用上也比较麻烦。而且在Java中,构造方法无法被继承,无法设置默认值。对于非必须的依赖处理,可能需要引入多个构造方法,而参数数量的变动可能造成维护上的不便。

setter 方法注入

FXNewsProvider
public class FXNewsProvider {
private IFXNewsListener newsListener;
private IFXNewsPersister newPersistener;

public IFXNewsListener getNewsListener() {
return newsListener;
}
public void setNewsListener(IFXNewsListener newsListener) {
this.newsListener = newsListener;
}

public IFXNewsPersister getNewPersistener() {
return newPersistener;
}
public void setNewPersistener(IFXNewsPersister newPersistener) {
this.newPersistener = newPersistener;
}
}
  • 优点:因为方法可以命名, 所以setter方法注入在描述性上要比构造方法注入好一些。 另外, setter方法可以被继承,允许设置默认值,而且有良好的IDE支持。
  • 缺点:当然就是对象无法在构造完成后马上进入就绪状态。

接口注入

FXNewsProvider为了让IoC Service Provider为其注入所依赖的IFXNewsListener,首先需要实现IFXNewsListenerCallable接口,这个接口会声明一个injectNewsListner方法(方法名随意),该方法的参数,就是所依赖对象的类型。这样, InjectionServiceContainer对象,即对应的IoCService Provider就可以通过这个接口方法将依赖对象注入到被注入对象FXNewsProvider当中。

  • 缺点接口注入是现在不甚提倡的一种方式,基本处于“退役状态”。因为它强制被注入对象实现不必要的接口,带有侵入性。而构造方法注入和setter方法注入则不需要如此。

IoC Service Provider

IoC Service Provider的职责是什么?

  • 业务对象的构建管理:在IoC场景中,业务对象无需关心所依赖对象如何构建如何获得,但这部分工作始终需要有人来做。所以,IoC Service Provider需要将对象的构建逻辑从客户端那里剥离出来,以免这部分逻辑污染业务对象的实现。 业务对象间的依赖绑定:
  • 业务对象的依赖管理:IoC Service Provider 通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别依赖关系,将这些对象所依赖的对象注绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态。

如何记录对象之间的依赖关系?

  • 它可以通过最基本的文本文件来记录被注入对象和其依赖对象之间的对应关系;
  • 它也可以通过描述性较强的XML文件格式来记录对应信息;
  • 它还可以通过编写代码的方式来注册这些对应信息;

Spring IoC容器 和 IoC Service Provider之间的关系

Spring的IoC容器是一个IoC Service Provider,但是,这只是它被冠以IoC之名的部分原因,我们不能忽略的是“容器”。 Spring的IoC容器是一个提供IoC支持的轻量级容器,除了基本的IoC支持,它作为轻量级容器还提供了IoC之外的支持。如在Spring的IoC容器之上, Spring还提供了相应的AOP框架支持、企业级服务集成等服务。

IoC容器和Provider的 关系

Spring提供了BeanFactory 和 ApplicationContext

  • BeanFactory: 基础类型IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延迟初始化策略( lazy-load )。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需要的资源有限。对于资源有限,并且功能要求不是很严格的场景, BeanFactory是比较合适的IoC容器选择。

  • ApplicationContext: ApplicationContext在BeanFactory的基础上构建,是相对比较高级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其他高级特性,比如事件发布、国际化信息支持等,这些会在后面详述。 ApplicationContext所管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成。所以,相对于BeanFactory来说, ApplicationContext要求更多的系统资源,同时,因为在启动时就完成所有初始化,容器启动时间较之BeanFactory也会长一些。在那些系统资源充足,并且要求更多功能的场景中,ApplicationContext类型的容器是比较合适的选择。

  • 作为Spring提供的基本的IoC容器,BeanFactory可以完成作为IoC Service Provider的所有职责,包括业务对象的注册和对象间依赖关系的绑定。

BeanFactory的对象注册与依赖绑定方式

整体依赖设计
// 1-设计FXNewsProvider类用于普遍的新闻处理
public class FXNewsProvider{
//...
}
// 2-设计IFXNewsListener接口抽象各个新闻社不同的新闻获取方式,并给出相应实现类
public interface IFXNewsListener{
//...
}
// 以及
public class DowJonesNewsListener implements IFXNewsListener {
//...
}
// 3-设计IFXNewsPersister接口抽象不同数据访问方式,并实现相应的实现类
public interface IFXNewsPersister {
//...
}
// 以及
public class DowJonesNewsPersister implements IFXNewsPersister {
//...
}

直接编码方式

1. 通过直接编码方式使用BeanFactory实现FX新闻相关类的注册及绑定
package org.springframework.mylearntest.beanf;

import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.mylearntest.before.FXNewsProvider;

public class BeanFactoryFX {
public static void main(String[] args) {
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
BeanFactory container = bindViaCode(beanRegistry);
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");
newsProvider.getAndPersistNews();
}

// 因为传入的DefaultListableBeanFactory同
// 时实现了BeanFactory和BeanDefinitionRegistry接口,所以,这样做强制类型转换不会出
// 现问题。但需要注意的是,单纯的BeanDefinitionRegistry是无法强制转换到BeanFactory
// 类型的!
public static BeanFactory bindViaCode(BeanDefinitionRegistry registry) {
AbstractBeanDefinition newsProvider = new RootBeanDefinition(FXNewsProvider.class, 0, true);
AbstractBeanDefinition newsListener = new RootBeanDefinition(DowJonesNewsListener.class,0, true);
AbstractBeanDefinition newsPersister = new RootBeanDefinition(DowJonesNewsPersister.class, 0,true);
// 1.将bean定义注册到容器中
registry.registerBeanDefinition("djNewsProvider", newsProvider);
registry.registerBeanDefinition("djListener", newsListener);
registry.registerBeanDefinition("djPersister", newsPersister);
// 2.0 指定依赖关系
// 2.1 可以通过构造方法注入方式
/*ConstructorArgumentValues argValues = new ConstructorArgumentValues();
argValues.addIndexedArgumentValue(0, newsListener);
argValues.addIndexedArgumentValue(1, newsPersister);
newsProvider.setConstructorArgumentValues(argValues);*/
// 2.2 或者通过setter方法注入方式
// 使newsListener newPersistener绑定到newsProvider上
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue(new PropertyValue("newsListener",newsListener));
propertyValues.addPropertyValue(new PropertyValue("newPersistener",newsPersister));
newsProvider.setPropertyValues(propertyValues);
// 3.0 绑定完成
return (BeanFactory)registry;
}
}
2. 设计IFXNewsListener接口抽象各个新闻社不同的新闻获取方式,并给出相应实现类
package org.springframework.mylearntest.before;

public interface IFXNewsListener {
String[] getAvailableNewsIds();

FXNewsBean getNewsByPK(String newsId);

void postProcessIfNecessary(String newsId);
}
package org.springframework.mylearntest.beanf;

import org.springframework.mylearntest.before.FXNewsBean;
import org.springframework.mylearntest.before.IFXNewsListener;

public class DowJonesNewsListener implements IFXNewsListener {
@Override
public String[] getAvailableNewsIds() {
return new String[0];
}

@Override
public FXNewsBean getNewsByPK(String newsId) {
return null;
}

@Override
public void postProcessIfNecessary(String newsId) {

}
}
3. 设计IFXNewsPersister接口抽象不同数据访问方式,并实现相应的实现类
package org.springframework.mylearntest.before;

public interface IFXNewsPersister {
void persistNews(FXNewsBean newsBean);
}
package org.springframework.mylearntest.beanf;

import org.springframework.mylearntest.before.FXNewsBean;
import org.springframework.mylearntest.before.IFXNewsPersister;

public class DowJonesNewsPersister implements IFXNewsPersister {
@Override
public void persistNews(FXNewsBean newsBean) {

}
}
4. 设计新闻提供类依赖于新闻监听和持久化类
package org.springframework.mylearntest.before;

import org.apache.commons.lang3.ArrayUtils;


public class FXNewsProvider {
private IFXNewsListener newsListener;
private IFXNewsPersister newPersistener;
public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister) {
this.newsListener = newsListner;
this.newPersistener = newsPersister;
}

public IFXNewsListener getNewsListener() {
return newsListener;
}

public void setNewsListener(IFXNewsListener newsListener) {
this.newsListener = newsListener;
}

public IFXNewsPersister getNewPersistener() {
return newPersistener;
}

public void setNewPersistener(IFXNewsPersister newPersistener) {
this.newPersistener = newPersistener;
}

public FXNewsProvider() {
}

public void getAndPersistNews() {
String[] newsIds = newsListener.getAvailableNewsIds();
if (ArrayUtils.isEmpty(newsIds)) {
return;
}
for (String newsId : newsIds) {
FXNewsBean newsBean = newsListener.getNewsByPK(newsId);
newPersistener.persistNews(newsBean);
newsListener.postProcessIfNecessary(newsId);
}
}
}
5. 设置新闻类
package org.springframework.mylearntest.before;

public class FXNewsBean {
}

外部配置文件方式

通常情况下,需要根据不同的外部配置文件格式,给出相应的BeanDefinitionReader实现类,由BeanDefinitionReader的相应实现类负责将相应的配置文件内容读取并映射到BeanDefinition,然后将映射后的BeanDefinition注册到一个BeanDefinitionRegistry,之后, BeanDefinitionRegistry即完成Bean的注册和加载。大部分工作,包括解析文件格式、装配BeanDefinition之类的工作,都是由BeanDefinitionReader的相应实现类来做的, BeanDefinitionRegistry只不过负责保管而已。

properties配置文件方式

binding-config.properties
djNewsProvider.(class)=org.springframework.mylearntest.ioc.directcode.FXNewsProvider
djNewsProvider.$0(ref)=djListener
djNewsProvider.$1(ref)=djPersister
# djNewsProvider.newsListener(ref)=djListener
# djNewsProvider.newPersistener(ref)=djPersister
djListener.(class)=org.springframework.mylearntest.ioc.propconfig.DjNewsListener
djPersister.(class)=org.springframework.mylearntest.ioc.propconfig.DjNewsPersister
PropConfigTest
package org.springframework.mylearntest.directcode;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader;

public class PropConfigTest {
public static void main(String[] args) {
// todo Caused by: java.lang.IllegalStateException: No bean class specified on bean definition
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
BeanFactory container = bindViaPropertiesFile(beanRegistry);
FXNewsProvider newsProvider =
(FXNewsProvider)container.getBean("djNewsProvider");
newsProvider.getAndPersistNews();
}

public static BeanFactory bindViaPropertiesFile(BeanDefinitionRegistry registry) {
PropertiesBeanDefinitionReader reader =
new PropertiesBeanDefinitionReader(registry);
reader.loadBeanDefinitions("classpath:binding-config.properties");
return (BeanFactory)registry;
}
}

@deprecated as of 5.3, in favor of Spring's common bean definition formats

参考资料

  1. 书籍名称:Spring揭秘 作者:王福强