Spring揭秘 学习笔记一 (Spring的IoC容器 一)

来源:互联网 发布:数据采集平台 编辑:程序博客网 时间:2024/05/22 01:08

Spring框架为POJO提供的各种服务共同组成了Spring的生命之树,如图1-1所示。
这里写图片描述

第2章 IoC的基本概念

2.1

IoC全称为Inversion of Control,中文通常翻译为“控制反转”,它还有一个别名叫做依赖注入(Dependency Injection)

为了更好地阐述IoC模式的概念,我们引入以下简单场景。
FX项目,经常需要近乎实时地为客户提供外汇新闻。通常情况下,都是先从不同的新闻社订阅新闻来源,然后通过批处理程序定时地到指定的新闻服务器抓取最新的外汇新闻,接着将这些新闻存入本地数据库,最后在FX系统的前台界面显示。
其中,FXNewsProvider需要依赖IFXNewsListener来帮助抓取新闻内容,并依赖IFXNewsPersister存储抓取的新闻。

//  代码清单2-1 FXNewsProvider类的实现public class FXNewsProvider{  private IFXNewsListener newsListener;  private IFXNewsPersister newPersistener;  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);   }  }}

如果我们依赖于某个类或服务,最简单而有效的方式就是直接在类的构造函数中新建相应的依赖类。

//   代码清单2-2 构造IFXNewsProvider类的依赖类public FXNewsProvider() {  newsListener = new DowJonesNewsListener();  newPersistener = new DowJonesNewsPersister();}

我们通常直接调用依赖对象所提供的某项服务,与其依赖对象都要主动地去获取,我们可以让IoC将某个依赖对象送过来

IoC的理念就是,让别人为你服务!在图2-1中,也就是让IoC Service Provider来为你服务!
这里写图片描述
通常情况下,被注入对象会直接依赖于被依赖对象。但是,在IoC的场景中,二者之间通过IoC ServiceProvider来打交道,所有的被注入对象和依赖对象现在由IoC Service Provider统一管理。

2.2

三种依赖注入的方式,即构造方法注入(constructor injection)、setter方法注入(setter injection)以及接口注入(interface injection)。

2.2.1 构造方法注入

构造方法注入,就是被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是IoC容器)知道它需要哪些依赖对象

FXNewsProvider是被注入对象,newsListner和newsPersister是被依赖对象

//  代码清单2-3 FXNewsProvider构造方法定义public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister) {   this.newsListener = newsListner;   this.newPersistener = newsPersister;}

IoC Service Provider会检查被注入对象的构造方法,取得它所需要的依赖对象列表,进而为其注入相应的对象。同一个对象是不可能被构造两次的,因此,被注入对象的构造乃至其整个生命周期,应该是由IoC Service Provider来管理的。

这就好比你刚进酒吧的门,服务生已经将你喜欢的啤酒摆上了桌面一样。坐下就可马上享受一份清凉与惬意

2.2.2 setter方法注入

对于JavaBean对象来说,通常会通过setXXX()和getXXX()方法来访问对应属性。这些setXXX()方法统称为setter方法,getXXX()当然就称为getter方法。通过setter方法,可以更改相应的对象属性,通过getter方法,可以获得相应属性的状态。所以,当前对象只要为其依赖对象所对应的属性添加setter
方法,就可以通过setter方法将相应的依赖对象设置到被注入对象中。

//  代码清单2-4 添加了setter方法声明的FXNewsProviderpublic 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;   }}

这样,外界就可以通过调用setNewsListener和setNewPersistener方法为FXNewsProvider对象注入所依赖的对象了。

这就好比你可以到酒吧坐下后再决定要点什么啤酒,可以要百威,也可以要大雪,随意性比较强。如果你不急着喝,这种方式当然是最适合你的。

2.2.3 接口注入

被注入对象如果想要IoC ServiceProvider为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。IoC Service Provider最终通过这些接口来了解应该为被注入对象注入什么依赖对象。
图2-3演示了如何使用接口注入为FXNewsProvider注入依赖对象。
这里写图片描述
FXNewsProvider为了让IoC Service Provider为其注入所依赖的IFXNewsListener,首先需要实现IFXNewsListenerCallable接口,这个接口会声明一个injectNewsListner方法(方法名随意),该方法的参数,就是所依赖对象的类型。这样,InjectionServiceContainer对象,即对应的IoC Service Provider就可以通过这个接口方法将依赖对象注入到被注入对FXNewsProvider当中。

接口中声明方法的参数类型,必须是“被注入对象”所依赖对象的类型。

这就好像你同样在酒吧点啤酒,为了让服务生理解你的意思,你就必须戴上一顶啤酒杯式的帽子

2.2.4 三种注入方式的比较

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

 构造方法注入。这种注入方式的优点就是,对象在构造完成之后,即已进入就绪状态,可以 马上使用。缺点就是,当依赖对象比较多的时候,构造方法的参数列表会比较长。而通过反射构造对象的时候,对相同类型的参数的处理会比较困难,维护和使用上也比较麻烦。而且在Java中,构造方法无法被继承,无法设置默认值。对于非必须的依赖处理,可能需要引入多个构造方法,而参数数量的变动可能造成维护上的不便。

 setter方法注入。因为方法可以命名,所以setter方法注入在描述性上要比构造方法注入好一些。 另外,setter方法可以被继承,允许设置默认值,而且有良好的IDE支持。缺点当然就是对象无法在构造完成后马上进入就绪状态。

综上所述,构造方法注入和setter方法注入因为其侵入性较弱,且易于理解和使用,所以是现在使用最多的注入方式;而接口注入因为侵入性较强,近年来已经不流行了

2.3 IoC 的附加值

IoC是一种可以帮助我们解耦各业务对象间依赖关系的对象绑定方式!不会对业务对象构成很强的侵入性,使用IoC后,对象具有更好的可测试性、可重用性和可扩展性,等等

如果有其他需求或变动(如多了新闻社叫MarketWin24。),没有用IoC时,对象跟DowJonesNewsListener是绑定的, 我们无法重用这个类了,为了解决问题, 我们可能要重新实现一个继承自FXNewsProvider的MarketWin24NewsProvider,或者干脆重新写一个类似的功能

而使用IoC,却完全可以不做任何改动,处理逻辑实际上应该是一样的:根据
各个公司的连接接口取得新闻,然后将取得的新闻存入数据库

因此,我们只要根据MarketWin24的新闻服务接口,为MarketWin24的FXNewsProvider提供相应的MarketWin24NewsListener注入就可
以了

//   代码清单2-5 构建在IoC之上可重用的FXNewsProvider使用演示FXNewsProvider dowJonesNewsProvider = ➥new FXNewsProvider(new DowJonesNewsListener(),new DowJonesNewsPersister());...FXNewsPrivider marketWin24NewsProvider = ➥new FXNewsProvider(new MarketWin24NewsListener(),new DowJonesNewsPersister());...

TDD(Test Driven Developement ,测试驱动开发)已经成为越来越受重视
的一种开发方式,保证业务对象拥有良好的可测试性,可以为最终交付高质量的软件奠定良好的基础,同时也拉起了产品质量的第一道安全网

设计开发可测试性良好的业务对象是至关重要的。而IoC模式可以让我们更容易达到这个目的

第三章 掌管大局的IoC Service Provider

IoC Service Provider在这里是一个抽象出来的概念,它可以指代任何将IoC场景中的业务对象绑定到一起的实现方式。它可以是一段代码,也可以是一组相关的类,甚至可以是比较通用的IoC框架或者IoC容器实现。

比如,可以通过以下代码(见代码清单3-1)绑定与新闻相关的对象。

//     代码清单3-1 FXNewsProvider相关依赖绑定代码IFXNewsListener newsListener = new DowJonesNewsListener();IFXNewsPersister newsPersister = new DowJonesNewsPersister();FXNewsProvider newsProvider = new FXNewsProvider(newsListener,newsPersister);newsProvider.getAndPersistNews();

这段代码就可以认为是这个场景中的IoC Service Provider,只不过比较简单,而且目的也过于单一。
Spring的IoC容器就是一个提供依赖注入服务的IoC Service Provider。

3.1 IoC Service Provider 的职责

IoC Service Provider的职责相对来说比较简单,主要有两个:业务对象的构建管理和业务对象间的依赖绑定。

业务对象的构建管理。在IoC场景中,业务对象无需关心所依赖的对象如何构建如何取得,但这部分工作始终需要有人来做。所以,IoC Service Provider需要将对象的构建逻辑从客户端对象那里剥离出来,以免这部分逻辑污染业务对象的实现。
业务对象间的依赖绑定。如果不能完成这个职责,那么,无论业务对象如何的“呼喊”,也不会得到依赖对象的任何响应(最常见的倒是会收到一个NullPointerException)。IoC Service Provider通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象注入绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态。

3.2 运筹帷幄的秘密——IoC Service Provider 如何管理对象间的依赖关系

服务生最终必须知道顾客点的饮品与库存饮品的对应关系,才能为顾客端上适当的饮品。对于为被注入对象提供依赖注入的IoC Service Provider来说,它也同样需要知道自己所管理和掌握的被注入对象和依赖对象之间的对应关系。

IoC Service Provider有几种方式来记录诸多对象之间的对应关系。比如:
①它可以通过最基本的文本文件来记录被注入对象和其依赖对象之间的对应关系;
②它也可以通过描述性较强的XML文件格式来记录对应信息;
③它还可以通过编写代码的方式来注册这些对应信息;
④它也可以通过语音方式来记录对象间的依赖注入关系

3.2.1 直接编码方式

当前大部分的IoC容器都应该支持直接编码方式,比如PicoContainer、Spring、Avalon等

在容器启动之前,我们就可以通过程序编码的方式将被注入对象和依赖对象注册到容器中,并明确它们相互之间的依赖注入关系

//代码清单3-2 直接编码方式管理对象间的依赖注入关系IoContainer container = ...;container.register(FXNewsProvider.class,new FXNewsProvider());container.register(IFXNewsListener.class,new DowJonesNewsListener());...FXNewsProvider newsProvider = (FXNewsProvider)container.get(FXNewsProvider.class);newProvider.getAndPersistNews();

如果是接口注入,可能伪代码看起来要多一些。不过,道理上是一样的,只不过除了注册相应对象,还要将“注入标志接口”与相应的依赖对象绑定一下,才能让容器最终知道是一个什么样的对应
关系,如代码清单3-3所演示的那样

//  代码清单3-3 直接编码形式管理基于接口注入的依赖注入关系IoContainer container = ...;container.register(FXNewsProvider.class,new FXNewsProvider());container.register(IFXNewsListener.class,new DowJonesNewsListener());...container.bind(IFXNewsListenerCallable.class, container.get(IFXNewsListener.class));...FXNewsProvider newsProvider = (FXNewsProvider)container.get(FXNewsProvider.class);newProvider.getAndPersistNews();

通过bind方法将“被注入对象”(由IFXNewsListenerCallable接口添加标志)所依赖的对象,绑定为容器中注册过的IFXNewsListener类型的对象实例。容器在返回FXNewsProvider对象实例之前,会根据这个绑定信息,将IFXNewsListener注册到容器中的对象实例注入到“被注入对象”——FXNewsProvider中,并最终返回已经组装完毕的FXNewsProvider对象。

3.2.2 配置文件方式

像普通文本文件、properties文件、XML文件等,都可以成为管理依赖注入关系的载体。不过,最为常见的,还是通过XML文件来管理对象注册和对象间依赖关系,比如Spring IoC容器和在PicoContainer基础上扩展的NanoContainer,都是采用XML文件来管理和保存依赖注入信息的。对于我们例子中的FXNewsProvider来说,也可以通过Spring配置文件的方式(见代码清单3-4)来配置和管理各个对象间的依赖关系

//代码清单3-4 通过Spring的配置方式来管理FXNewsProvider的依赖注入关系<bean id="newsProvider" class="..FXNewsProvider">  <property name="newsListener">    <ref bean="djNewsListener"/>  </property>  <property name="newPersistener">    <ref bean="djNewsPersister"/>  </property></bean><bean id="djNewsListener"  class="..impl.DowJonesNewsListener"></bean><bean id="djNewsPersister"  class="..impl.DowJonesNewsPersister"></bean>

最后,我们就可以像代码清单3-5所示的那样,通过“newsProvider”这个名字,从容器中取得已经组装好的FXNewsProvider并直接使用。

//代码清单3-5 从读取配置文件完成对象组装的容器中获取FXNewsProvider并使用...container.readConfigurationFiles(...);FXNewsProvider newsProvider =(FXNewsProvider)container.getBean("newsProvider");newsProvider.getAndPersistNews();

3.2.3 元数据方式

这种方式的代表实现是Google Guice,这是Bob Lee在Java 5的注解和Generic的基础上开发的一套IoC框架。我们可以直接在类中使用元数据信息来标注各个对象之间的依赖关系,然后由Guice框架根据这些注解所提供的信息将这些对象组装后,交给客户端对象使用。代码清单3-6演示了使用Guice的相应注解标注后的FXNewsProvider定义。

//代码清单3-6 使用Guice的注解标注依赖关系后的FXNewsProvider定义public class FXNewsProvider{   private IFXNewsListener newsListener;  private IFXNewsPersister newPersistener;  @Inject   public FXNewsProvider(IFXNewsListener listener,IFXNewsPersister persister) {  this.newsListener = listener;  this.newPersistener = persister;   } ...}

通过@Inject,我们指明需要IoC Service Provider通过构造方法注入方式,为FXNewsProvider注入其所依赖的对象。至于余下的依赖相关信息,在Guice中是由相应的Module来提供的,代码清单3-7给出了FXNewsProvider所使用的Module实现。

// 代码清单3-7 FXNewsProvider所使用的Module实现public class NewsBindingModule extends AbstractModule{ @Override protected void configure() {   bind(IFXNewsListener.class)     .to(DowJonesNewsListener.class).in(Scopes.SINGLETON);   bind(IFXNewsPersister.class)     .to(DowJonesNewsPersister.class).in(Scopes.SINGLETON); } }

通过Module指定进一步的依赖注入相关信息之后,我们就可以直接从Guice那里取得最终已经注入完毕,并直接可用的对象了(见代码清单3-8)

//  代码清单3-8 从Guice获取并使用最终绑定完成的FXNewsProviderInjector injector = Guice.createInjector(new NewsBindingModule());FXNewsProvider newsProvider = injector.getInstance(FXNewsProvider.class);newsProvider.getAndPersistNews();

第4 章 Spring的IoC容器之BeanFactory

Spring的IoC容器是一个提供IoC支持的轻量级容器,除了基本的IoC支持,它作为轻量级容器还提供了IoC之外的支持。如在Spring的IoC容器之上,Spring还提供了相应的AOP(Aspect Oriented Programming,面向切面编程)框架支持、企业级服务集成等服务。Spring的IoC容器和IoC Service Provider所提供的服务之间存在一定的交集,二者的关系如图4-1所示。
这里写图片描述

Spring提供了两种容器类型:BeanFactory和ApplicationContext。
①BeanFactory。基础类型IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延迟初始化策略(lazy-load)。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需要的资源有限。对于资源有限,并且功能要求不是很严格的场景,BeanFactory是比较合适的IoC容器选择
②ApplicationContext。ApplicationContext在BeanFactory的基础上构建,是相对比较高级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其他高级特性,比如事件发布、国际化信息支持等。ApplicationContext所管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成。所以,相对于BeanFactory来说,ApplicationContext要求更多的系统资源,同时,因为在启动时就完成所有初始化,容器启动时间较之BeanFactory也会长一些。在那些系统资源充足,并且要求更多功能的场景中,ApplicationContext类型的容器是比较合适的选择

通过图4-2,我们可以对BeanFactory和ApplicationContext之间的关系有一个更清晰的认识。
这里写图片描述

BeanFactory,顾名思义,就是生产Bean的工厂。作为Spring提供的基本的IoC容器,BeanFactory可以完成作为IoC Service Provider的所有职责,包括业务对象的注册和对象间依赖关系的绑定。

BeanFactory会公开一个取得组装完成的对象的方法接口,就像代码清单4-1中真正的BeanFactory的定义所展示的那样

// 代码清单4-1 BeanFactory的定义public interface BeanFactory {  String FACTORY_BEAN_PREFIX = "&";   Object getBean(String name) throws   BeansException;  Object getBean(String name, Class requiredType) throws BeansException;  /**  * @since 2.5  */  Object getBean(String name, Object[] args) throws BeansException;  boolean containsBean(String name);  boolean isSingleton(String name) throws NoSuchBeanDefinitionException;  /**  * @since 2.0.3  */  boolean isPrototype(String name) throws NoSuchBeanDefinitionException;  /**  * @since 2.0.1  */  boolean isTypeMatch(String name, Class targetType) throws NoSuchBeanDefinitionException;  Class getType(String name) throws NoSuchBeanDefinitionException;  String[] getAliases(String name);}

上面代码中的方法基本上都是查询相关的方法,例如,取得某个对象的方法(getBean)、查询
某个对象是否存在于容器中的方法(containsBean),或者取得某个bean的状态或者类型的方法等。

4.1 拥有BeanFactory之后的生活

拥有BeanFactory之后,要使用IoC模式进行系统业务对象的开发。(实际上,即使不使用BeanFactory之类的轻量级容器支持开发,开发中也应该尽量使用IoC模式。)

代码清单4-2演示了FX新闻系统初期的设计和实现框架代码。

// 代码清单4-2 FX新闻应用设计和实现框架代码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{...}

通常情况下,BeanFactory会通过常用的XML文件来注册并管理各个业务对象之间的依赖关系

//  代码清单4-3 使用BeanFactory的XML配置方式实现业务对象间的依赖管理<beans>  <bean id="djNewsProvider" class="..FXNewsProvider"> 8    <constructor-arg index="0">      <ref bean="djNewsListener"/>    </constructor-arg> <constructor-arg index="1"> 9      <ref bean="djNewsPersister"/>    </constructor-arg>  </bean>...</beans>

有了BeanFactory,我们通常只需将“生产线图纸”交给BeanFactory,让BeanFactory为我们生产一个FXNewsProvider,如以下代码所示:

BeanFactory container = new XmlBeanFactory(new ClassPathResource("配置文件路径"));//或ApplicationContext container = new ClassPathXmlApplicationContext("配置文件路径");//或ApplicationContext container =new FileSystemXmlApplicationContext("配置文件路径");FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");newsProvider.getAndPersistNews();

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

BeanFactory作为一个IoC Service Provider,为了能够明确管理各个业务对象以及业务对象之间的
依赖绑定关系,同样需要某种途径来记录和管理这些信息。

4.2.1 直接编码方式

下面来看一下我们的FX新闻系统相关类是如何注册并绑定的

// 代码清单4-4 通过编码方式使用BeanFactory实现FX新闻相关类的注册及绑定public static void main(String[] args){  DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();  BeanFactory container = (BeanFactory)bindViaCode(beanRegistry);  FXNewsProvider newsProvider =   (FXNewsProvider)container.getBean("djNewsProvider");  newsProvider.getAndPersistNews();}public static BeanFactory bindViaCode(BeanDefinitionRegistry registry){  AbstractBeanDefinition newsProvider =   new RootBeanDefinition(FXNewsProvider.class,true);  AbstractBeanDefinition newsListener =   new RootBeanDefinition(DowJonesNewsListener.class,true);  AbstractBeanDefinition newsPersister =   new RootBeanDefinition(DowJonesNewsPersister.class,true);  // 将bean定义注册到容器中  registry.registerBeanDefinition("djNewsProvider", newsProvider);  registry.registerBeanDefinition("djListener", newsListener);  registry.registerBeanDefinition("djPersister", newsPersister);// 指定依赖关系  // 1. 可以通过构造方法注入方式  ConstructorArgumentValues argValues = new ConstructorArgumentValues();  argValues.addIndexedArgumentValue(0, newsListener);  argValues.addIndexedArgumentValue(1, newsPersister);  newsProvider.setConstructorArgumentValues(argValues);  // 2. 或者通过setter方法注入方式  MutablePropertyValues propertyValues = new MutablePropertyValues();  propertyValues.addPropertyValue(new PropertyValue("newsListener",newsListener));  propertyValues.addPropertyValue(new PropertyValue("newPersistener",newsPersister));  newsProvider.setPropertyValues(propertyValues);  // 绑定完成   return (BeanFactory)registry;}

BeanFactory只是一个接口,我们最终需要一个该接口的实现来进行实际的Bean的管理,DefaultListableBeanFactory就是这么一个比较通用的BeanFactory实现类。DefaultListableBeanFactory除了间接地实现了BeanFactory接口,还实现了BeanDefinitionRegistry接口,该接口才是在BeanFactory的实现中担当Bean注册管理的角色。

基本上,BeanFactory接口只定义如何访问容器内管理的Bean的方法,各个BeanFactory的具体实现类负责具体Bean的注册以及管理工作。BeanDefinitionRegistry接口定义抽象了Bean的注册逻辑。通常情况下,具体的BeanFactory实现类会实现这个接口来管理Bean的注册。

它们之间的关系如图4-3所示
这里写图片描述

每一个受管的对象,在容器中都会有一个BeanDefinition的实例(instance)与之相对应,该BeanDefinition的实例负责保存对象的所有必要信息,包括其对应的对象的class类型、是否是抽象类、构造方法参数以及其他属性等。当客户端向BeanFactory请求相应对象的时候,BeanFactory会通过这些信息为客户端返回一个完备可用的对象实例。RootBeanDefinition和ChildBeanDefinition是BeanDefinition的两个主要实现类。

①在 main 方法中, 首先构造一个DefaultListableBeanFactory 作为BeanDefinitionRegistry,然后将其交给bindViaCode方法进行具体的对象注册和相关依赖管理,然后通过bindViaCode返回的BeanFactory取得需要的对象,最后执行相应逻辑。在我们的实例里,当然就是取得FXNewsProvider进行新闻的处理。

②在bindViaCode方法中,首先针对相应的业务对象构造与其相对应的BeanDefinition,使用了RootBeanDefinition 作为BeanDefinition 的实现类。构造完成后, 将这些BeanDefinition注册到通过方法参数传进来的BeanDefinitionRegistry中。之后,因为我们的FXNewsProvider是采用的构造方法注入,所以,需要通过ConstructorArgumentValues为其注入相关依赖。在这里为了同时说明setter方法注入,也同时展示了在Spring中如何使用代码实现setter方法注入。如果要运行这段代码,需要把setter方法注入部分的行代码注释掉。最后,以BeanFactory的形式返回已经注册并绑定了所有相关业务对象的BeanDefinitionRegistry实例。

因为传入的DefaultListableBeanFactory同时实现了BeanFactory和BeanDefinitionRegistry接口,所以,这样做强制类型转换不会出现问题。但需要注意的是,单纯的BeanDefinitionRegistry是无法强制转换到BeanFactory类型的!

4.2.2 外部配置文件方式

Spring的IoC容器支持两种配置文件格式:Properties文件格式和XML文件格式。

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

整个过程类似于如下代码:

BeanDefinitionRegistry beanRegistry = <某个BeanDefinitionRegistry实现类,通常为DefaultListableBeanFactory>;BeanDefinitionReader beanDefinitionReader = new BeanDefinitionReaderImpl(beanRegistry);beanDefinitionReader.loadBeanDefinitions("配置文件路径");// 现在我们就取得了一个可用的Bea nDefinitionRegistry实例

1. Properties配置格式的加载
Spring提供了org.springframework.beans.factory.support.PropertiesBeanDefinition-
Reader类用于Properties格式配置文件的加载,所以,我们不用自己去实现BeanDefinitionReader,
只要根据该类的读取规则,提供相应的配置文件即可。

# 代码清单4-5 Properties格式表达的依赖注入配置内容djNewsProvider.(class)=..FXNewsProvider# ----------通过构造方法注入的时候-------------djNewsProvider.$0(ref)=djListenerdjNewsProvider.$1(ref)=djPersister# ----------通过setter方法注入的时候---------# djNewsProvider.newsListener(ref)=djListener# djNewsProvider.newPersistener(ref)=djPersisterdjListener.(class)=..impl.DowJonesNewsListenerdjPersister.(class)=..impl.DowJon esNewsPersister

①djNewsProvider作为beanName,后面通过.(class)表明对应的实现类是什么,djListener和djPersister,也是相同的道理。
②通过在表示beanName的名称后添加.$[number]后缀的形式,来表示当前beanName对应的对象需要通过构造方法注入的方式注入相应依赖对象
需要注意的一点,就是$0后面的(ref),(ref)用来表示所依赖的是引用对象,而不是普通的类型。如果不加(ref),PropertiesBeanDefinitionReader会将djListener和djPersister作为简单的String类型进行注入,异常自然不可避免啦。
③setter方法注入不使用数字顺序来指定注入的位置,而使用相应的属性名称来指定注入,newsListener和
newPersistener恰好就是我们的FXNewsProvider类中所声明的属性名称。同样需要注意,如果属性名称所依赖的是引用对象,那么一定不要忘了(ref)。

//代码清单4-6 加载Properties配置的BeanFactory的使用演示public static void main(String[] args){   DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();  BeanFactory container = (BeanFactory)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;}

所有的信息配置到Properties文件即可,不用再通过冗长的代码来完成对象的注册和依赖绑定.
这些工作就交给相应的BeanDefinitionReader(这里是PropertiesBeanDefinitionReader)来做吧

2. XML配置格式的加载
如果FX新闻系统对象按照XML配置方式进行加载的话,配置文件内容如代码清单4-7所示。

<!-- 代码清单4-7 FX新闻系统相关类对应XML格式的配置内容--><?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans>  <bean id="djNewsProvider" class="..FXNewsProvider">    <constructor-arg index="0">      <ref bean="djNewsListener"/>    </constructor-arg>    <constructor-arg index="1">      <ref bean="djNewsPersister"/>    </constructor-arg>  </bean>  <bean id="djNewsListener" class="..impl.DowJonesNewsListener">  </bean>  <bean id="djNewsPersister" class="..impl.DowJonesNewsPersister">  </bean></beans>

代码清单4-8 加载XML配置文件的BeanFactory的使用演示

public static void main(String[] args){ DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory(); BeanFactory container = (BeanFactory)bindViaXMLFile(beanRegistry); FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider"); newsProvider.getAndPersistNews(); }public static BeanFactory bindViaXMLFile(BeanDefinitionRegistry registry){  XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry); reader.loadBeanDefinitions("classpath:../news-config.xml"); return (BeanFactory)registry; // 或者直接 //return new XmlBeanFactory(new ClassPathResource("../news-config.xml"));}

XmlBeanDefinitionReader负责读取Spring指定格式的XML配置文件并解析,之后将解析后的文件内容映射到相应的BeanDefinition,并加载到相应的BeanDefinitionRegistry中(在这里是DefaultListableBeanFactory)。这时,整个BeanFactory就可以放给客户端使用了。

从以上代码最后注释掉的一行,你可以看到使用了XmlBeanFactory之后,完成XML的加载和BeanFactory的初始化是多么简单。

4.2.3 注解方式
如果要通过注解标注的方式为FXNewsProvider注入所需要的依赖,现在可以使用@Autowired以及@Component对相关类进行标记。代码清单4-9演示了FXNews相关类使用指定注解标注后的情况。

//  代码清单4-9 使用指定注解标注后的FXNews相关类@Componentpublic class FXNewsProvider{   @Autowired  private IFXNewsListener newsListener;  @Autowired  private IFXNewsPersister newPersistener;  public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister)  {    this.newsListener = newsListner;    this.newPersistener = newsPersister;  }  ...}@Componentpublic class DowJonesNewsListener implements IFXNewsListener{  ...}@Componentpublic class DowJonesNewsPersister implements IFXNewsPersister{  ...}

@Autowired将告知Spring容器需要为当前对象注入哪些依赖对象。
@Component则是配合Spring 2.5中新的classpath-scanning功能使用的。现在我们只要再向Spring的配置文件中增加一个“触发器”,使用@Autowired和@Component标注的类就能获得依赖对象的注入了。

<!--代码清单4-10 配置使用classpath-scanning功能--><?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"><context:component-scan base-package="cn.spring21.project.base.package"/></beans>

<context:component-scan>会到指定的包(package)下面扫描标注有@Component的类,如果找到,则将它们添加到容器进行管理,并根据它们所标注的@Autowired为这些类注入符合条件的依赖对象。

在以上所有这些工作都完成之后,我们就可以像通常那样加载配置并执行当前应用程序了,如以下代码所示:

public static void main(String[] args){  ApplicationContext ctx = new ClassPathXmlApplicationContext("配置文件路径");  FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("FXNewsProvider");  newsProvider.getAndPersistNews();}

4.3.1 <beans>和<bean>

在Spring 2.0版本之前,所有的Spring容器加载的XML配置文件的头部,都需要以下形式的DOCTYPE声明:

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans>... </beans>

Spring 2.0版本之后,既可以继续使用DTD方式的DOCTYPE进行配置文件格式的限定,又引入了基于XML Schema的文档声明

使用代码清单4-11所展示的基于XSD的文档声明

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:lang="http://www.springframework.org/schema/lang" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.0.xsd  http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"></beans>

所有注册到容器的业务对象,在Spring中称之为Bean。所以,每一个对象在XML中的映射也自然
而然地对应一个叫做<bean>的元素。既然容器最终可以管理所有的业务对象,那么在XML中把这些
叫做的元素组织起来的,就叫做<beans>。

1.<beans>
<beans>是XML配置文件中最顶层的元素,它下面可以包含0或者1个<description>和多个<bean>以及<import>或者<alias>,如图4-4所示。
这里写图片描述
<beans>拥有相应的属性(attribute)对所辖的<bean>进行统一的默认行为设置,包括如下几个:
default-lazy-init。其值可以指定为true或者false,默认值为false。用来标志是否对所有的进行延迟初始化。
default-autowire。可以取值为no、byName、byType、constructor以及autodetect。默认值为no,如果使用自动绑定的话,用来标志全体bean使用哪一种默认绑定方式。
default-dependency-check。可以取值none、objects、simple以及all,默认值为none,即不做依赖检查。
default-init-method。如果所管辖的<bean>按照某种规则,都有同样名称的初始化方法的话,可以在这里统一指定这个初始化方法名,而不用在每一个<bean>上都重复单独指定。
default-destroy-method。与default-init-method相对应,如果所管辖的bean有按照某种规则使用了相同名称的对象销毁方法,可以通过这个属性统一指定。

2. <description>、<import>和<alias>
① <description>
可以通过<description>在配置的文件中指定一些描述性的信息。通常情况下,该元素是省略的。
②<import>
通常情况下,可以根据模块功能或者层次关系,将配置信息分门别类地放到多个配置文件中。在 5
想加载主要配置文件,并将主要配置文件所依赖的配置文件同时加载时,可以在这个主要的配置文件
中通过<import>元素对其所依赖的配置文件进行引用。
③<alias>
可以通过<alias>为某些<bean>起一些“外号”(别名),通常情况下是为了减少输入。比如,假设有个<bean>,它的名称为dataSourceForMasterDatabase,你可以为其添加一个<alias>,像这样<alias name=”dataSourceForMasterDatabase” alias=”masterDataSource”/>。

4.3.2 <beans>

如下代码演示了最基础的对象配置形式:

<bean id="djNewsListener" class="..impl.DowJonesNewsListener"> </bean>

id属性指定当前注册对象的beanName是什么
还可以使用name属性来指定的别名 (alias)

<bean id="djNewsListener"      name="/news/djNewsListener,dowJonesNewsListener"      class="..impl.DowJonesNewsListener"></bean>

与id属性相比,name属性的灵活之处在于,name可以使用id不能使用的一些字符,比如/。而且还可以通过逗号、空格或者冒号分割指定多个name。name的作用跟使用为id指定多个别名基本相同:

<alias name="djNewsListener" alias="/news/djNewsListener"/><alias name="djNewsListener" alias="dowJonesNewsListener"/>

每个注册到容器的对象都需要通过<bean>元素的class属性指定其类型,否则,容器可不知道这个对象是什么。

4.3.3 多个<bean>之间的关系:互相依赖,互相帮助以完成同一目标

对象之间需要相互协作,在横向上它们存在一定的依赖性。

1. 构造方法注入的XML之道
按照Spring的IoC容器配置格式,要通过构造方法注入方式,为当前业务对象注入其所依赖的对象,需要使用<constructor-arg>。

<bean id="djNewsProvider" class="..FXNewsProvider">  <constructor-arg ref="djNewsListener"/>  <constructor-arg ref="djNewsPersister"/></bean>

通过<constructor-arg>来指明容器将为djNewsProvider这个<bean>注入通过<ref>所引用的Bean实例。

容器在加载XML配置的时候,因为某些原因,无法明确配置项与对象的构造方法参数列表的一一对应关系,就需要请<constructor-arg>的type或者index属性出马。比如,对象存在多个构造方法,当参数列表数目相同而类型不同的时候,容器无法区分应该使用哪个构造方法来实例化对象,或者构造方法可能同时传入最少两个类型相同的对象。

①type属性
假设某个类声明了两个构造方法,分别都只是传入一个参数,且参数类型不同(例如一个String,一个int)。
我们要调用特定类型的构造函数,可以使用type属性,通过指定构造方法的参数类型来解决这一问题,配置内容如下代码所示:

<bean id="mockBO" class="..MockBusinessObject">  <constructor-arg type="int">    <value>111111</value>  </constructor-arg></bean>

②index属性
当某个业务对象的构造方法同时传入了多个类型相同的参数时,使用index属性,可以按自己意愿为特定下标的参数来构造对象:

<bean id="mockBO" class="..MockBusinessObject">  <constructor-arg index="1" value="11111"/>  <constructor-arg index="0" value="22222"/></bean>

2. setter方法注入的XML之道
与构造方法注入可以使用<constructor-arg>注入配置相对应,Spring为setter方法注入提供了<property>元素。
<property>有一个name属性(attribute),用来指定该<property>将会注入的对象所对应的实例变量名称。之后通过value或者ref属性或者内嵌的其他元素来指定具体的依赖对象引用或者值,如以下代码所示:

<bean id="djNewsProvider" class="..FXNewsProvider">  <property name="newsListener" ref="djNewsListener"/>   <property name="newPersistener" ref="djNewsPersister"/></bean>

3. <property>和<constructor-arg>中可用的配置项
(1) <value>
可以通过value为主体对象注入简单的数据类型,不但可以指定String类型的数据,
而且可以指定其他Java语言中的原始类型以及它们的包装器(wrapper)类型,比如int、Integer等。

<constructor-arg value="111111"/><property name="attributeName" value="222222"/>

需要说明的是,是最“底层”的元素,它内部不能再嵌套使用其他元素了。
(2) <ref>
使用ref来引用容器中其他的对象实例,可以通过ref的local、parent和bean属性来指定引用的对象的beanName是什么。

<constructor-arg>  <ref local="djNewsPersister"/></constructor-arg>或者<constructor-arg>  <ref parent="djNewsPersister"/></constructor-arg>或者<constructor-arg>  <ref bean="djNewsPersister"/></constructor-arg>

local、parent和bean的区别在于:
①local只能指定与当前配置的对象在同一个配置文件的对象定义的名称(可以获得XML解析器的id约束验证支持);
②parent则只能指定位于当前容器的父容器中定义的对象引用;
注意 BeanFactory可以分层次(通过实现HierarchicalBeanFactory接口),容器A在初始化的时候,可以首先加载容器B中的所有对象定义,然后再加载自身的对象定义,这样,容器B就成为了容器A的父容器,容器A可以引用容器B中的所有对象定义:

BeanFactory parentContainer = new XmlBeanFactory(new ClassPathResource("父容器配置文件路径"));BeanFactory childContainer = new XmlBeanFactory(new ClassPathResource("子容器配置文件路径"),parentContainer);

childContainer中定义的对象,如果通过parent指定依赖,则只能引用parentContainer中的对象定义。
③bean则基本上通吃,所以,通常情况下,直接使用bean来指定对象引用就可以了。

<ref>的定义为<!ELEMENT ref EMPTY>,也就是说,它下面没有其他子元素可用了.

(3) <idref>
如果要为当前对象注入所依赖的对象的名称,而不是引用,通常情况下可以使用<value>,但这种场合下,使用idref才是最为合适的。因为使用idref,容器在解析配置的时候就可以检查这个beanName到底是否存在,而不用等到运行时才发现这个beanName对应的对象实例不存在。

<property name="newsListenerBeanName">  <idref bean="djNewsListener"/></property>

这段配置跟上面使用达到了相同的目的,不过更加保险

(4) 内部<bean>
使用<ref>可以引用容器中独立定义的对象定义。但有时,可能我们所依赖的对 象只有当前一个对象引用,或者某个对象定义我们不想其他对象通过<ref>引用到它。这时,我们可以使用内嵌的<bean>,将这个私有的对象定义仅局限在当前对象。

<!--代码清单4-16 内部<bean>的配置演示 --><bean id="djNewsProvider" class="..FXNewsProvider"> 10  <constructor-arg index="0">   <bean class="..impl.DowJonesNewsListener">   </bean>   </constructor-arg>   <constructor-arg index="1">    <ref bean="djNewsPersister"/>  </constructor-arg></bean>

这样,该对象实例就只有当前的djNewsProvider可以使用,其他对象无法取得该对象的引用。

如果内部<bean>对应的对象还依赖于其他对象,你完全可以像其他独立的<bean>定义一样为其配置相关依赖,没有任何差别。

(5) <list>
<list>对应注入对象类型为java.util.List及其子类或者数组类型的依赖对象。通过<list>可以有序地为当前对象注入以collection形式声明的依赖

public class MockDemoObject{  private List param1;  private String[] param2;  ...  // 相应的setter和getter方法  ...}

配置类似于

<!-- 代码清单4-17 使用<list>进行依赖注入的对象定义以及相关配置内容--><property name="param1">  <list>    <value> something</value>    <ref bean="someBeanName"/>    <bean class="..."/>  </list></property><property name="param2">  <list>    <value>stringValue1</value>    <value>stringValue2</value>  </list></property>

(6) <set>
如果说<list>可以帮你有序地注入一系列依赖的话,那么<set>就是无序的,而且,对于set来说,元素的顺序本来就是无关紧要的。<set>对应注入Java Collection中类型为java.util.Set或者其子类的依赖对象

<!-- 代码清单4-18 使用<set>进行依赖注入的对象定义以及相关配置内容--><property name="valueSet">  <set>    <value> something</value>    <ref bean="someBeanName"/>    <bean class="..."/>    <list>    ...    </list>  </set></property>

(7) <map>
映射(map)可以通过指定的键(key)来获取相应的值。<map>与<list>和<set>的相同点在于,都是为主体对象注入Collection类型的依赖,不同点在于它对应注入java.util.Map或者其子类类型的依赖对象。

<property name="mapping">  <map>   <entry key="strValueKey">   <value>something</value>  </entry>   <entry>   <key>objectKey</key>   <ref bean="someObject"/>   </entry>  <entry key-ref="lstKey">    <list>    ...    </list>  </entry>   ... </map> < /property>

对于<map>来说,它可以内嵌任意多个<entry>,每一个<entry>都需要为其指定一个键和一个值,就跟真正的java.util.Map所要求的一样。
①指定entry的键。可以使用<entry>的属性——key或者key-ref来指定键,也可以使用<entry>
的内嵌元素<key>来指定键
②指定entry对应的值。<entry>内部可以使用的元素,除了<key>是用来指定键的,其他元素
可以任意使用,来指定entry对应的值。

所以,如果你不想敲那么些字符,可以像代码清单4-20所展示的那样使用<map>进行依赖注入的配置

<property name="valueSet">  <map>    <entry key="strValueKey" value="something"/>    <entry key-ref="" value-ref="someObject"/>    <entry key-ref="lstKey">      <list>      ...      </list>    </entry>    ...  </map></property>

(8) <props>
<props>是简化后了的<map>,或者说是特殊化的map,该元素对应配置类型为java.util.Properties的对象依赖。因为Properties只能指定String类型的键(key)和值,所以,<props>的配置简化很多,只有固定的格式

<property name="valueSet"> <props>   <prop key="author">fujohnwang@gmail.com</prop>   <prop key="support">support@spring21.cn</prop>   ... </props></property>

每个<props>可以嵌套多个<prop>,每个<prop>通过其key属性来指定键,在<prop>内部直接指定其所对应的值

(9) <null/>
这是最简单的一个元素,因为它只是一个空元素.如果需要为某个对象对应的值注入null的话,请使用<null/>

4. depends-on
如果某个类非显式地依赖某个类,那么可以使用depends-on来要求容器在初始化自身实例之前首先实例化被依赖的类。

<bean id="classAInstance" class="...ClassA" depends-on="configSetup"/> <bean id="configSetup" class="SystemConfigurationSetup"/>

如果说ClassA拥有多个类似的非显式依赖关系,那么,你可以在ClassA的depends-on中通过逗号分割各个beanName。

5. autowire
通过<bean>的autowire属性,可以指定当前bean定义采用某种类型的自动绑定模式。这样,你就无需手工明确指定该bean定义相关的依赖关系,从而也可以免去一些手工输入的工作量。
Spring提供了5种自动绑定模式,即no、byName、byType、constructor和autodetect
①no
容器默认的自动绑定模式,也就是不采用任何形式的自动绑定,完全依赖手工明确配置各个bean之间的依赖关系

②byName
按照类中声明的实例变量的名称,与XML配置文件中声明的bean定义的beanName的值进行匹配,相匹配的bean定义将被自动绑定到当前实例变量上。这种方式对类定义和配置的bean定义有一定的限制。

假设我们有如下所示的类定义:

public class Foo{ private Bar emphasisAttribute; ... // 相应的setter方法定义}public class Bar{...}

那么应该使用如下代码所演示的自动绑定定义,才能达到预期的目的:

<bean id="fooBean" class="...Foo" autowire="byName"></bean><bean id="emphasisAttribute" class="...Bar"></bean>

对于byName模式中的实例类Foo来说,容器会在其所管理的所有bean定义中寻找类型为Bar的bean定义。如果找到,则将找到的bean绑定到Foo的bean定义;如果没有找到,则不做设置。但如果找到多个,容器会告诉你它解决不了“该选用哪一个”的问题,你只好自己查找原因,并自己修正该问题。

③byType
如果指定当前bean定义的autowire模式为byType,那么,容器会根据当前bean定义类型,分析其相应的依赖对象类型,然后到容器所管理的所有bean定义中寻找与依赖对象类型相同的bean定义,然后将找到的符合条件的bean自动绑定到当前bean定义。

byType只能保证,在容器中只存在一个符合条件的依赖对象的时候才会发挥最大的作用,如果
容器中存在多个相同类型的bean定义,那么,请手动设置。

④constructor
byName和byType类型的自动绑定模式是针对property的自动绑定,而constructor类型则是针对构造方法参数的类型而进行的自动绑定,它同样是byType类型的绑定模式。不过,constructor是匹配构造方法的参数类型,而不是实例属性的类型。与byType模式类似,如果找到不止一个符合条件的bean定义,那么,容器会返回错误

public class Foo{  private Bar bar;   public Foo(Bar arg)  {    this.bar = arg;  }  ...}

相应配置为

<bean id="foo" class="...Foo" autowire="constructor"/><bean id="bar" class="...Bar">< /bean>

⑤autodetect
这种模式是byType和constructor模式的结合体,如果对象拥有默认无参数的构造方法,容器会优先考虑byType的自动绑定模式。否则,会使用constructor模式。当然,如果通过构造方法注入绑定后还有其他属性没有绑定,容器也会使用byType对剩余的对象属性进行自动绑定。

<beans>有一个default-autowire属性,它可以帮我们省去为多个<bean>单独设置autowire属性的麻烦,default-autowire的默认值为no,即不进行自动绑定。

6. dependency-check
该功能主要与自动绑定结合使用,可以保证当自动绑定完成后,最终确认每个对象所依赖的对象是否按照所预期的那样被注入。
基本上有如下4种类型的依赖检查:
①none
不做依赖检查。默认情况下,容器以此为默认值。

②simple
如果将dependency-check的值指定为simple,那么容器会对简单属性类型以及相关的collection进行依赖检查,对象引用类型的依赖除外。

③object
只对对象引用类型依赖进行检查。

④all
将simple和object相结合,也就是说会对简单属性类型以及相应的collection和所有对象引用类型的依赖进行检查。

7. lazy-init
延迟初始化(lazy-init)这个特性的作用,主要是可以针对ApplicationContext容器的bean初始化行为施以更多控制。

与BeanFactory不同,ApplicationContext在容器启动的时候,就会马上对所有的“singleton的bean定义”进行实例化操作。
我们想改变某个或者某些bean定义在ApplicationContext容器中的默认实例化时机。这时,就可以通过<bean>的lazy-init属性来控制这种初始化行为,如下代码所示:

<bean id="lazy-init-bean" class="..." lazy-init="true"/><bean id="not-lazy-init-bean" class="..."/>

这样,ApplicationContext容器在启动的时候,只会默认实例化not-lazy-init-bean而不会实例化lazy-init-bean。

如果某个非延迟初始化的bean定义依赖于lazy-init-bean,那么毫无疑问,按照依赖决计的顺序,容器还是会首先实例化lazy-init-bean,然后再实例化后者,如下代码演示了这种相互牵连导致延迟初始化失败的情况:

<bean id="lazy-init-bean" class="..." lazy-init="true"/> <bean id="not-lazy-init-bean" class="..."> <property name="propName">   <ref bean="lazy-init-bean"/> </property></bean>

虽然lazy-init-bean是延迟初始化的,但因为依赖它的not-lazy-init-bean并不是延迟初始
化,所以lazy-init-bean还是会被提前初始化。如果我们真想保证lazy-init-bean一定会被延迟初始化的话,就需要保证依赖于该bean定义的其他bean定义也同样设置为延迟初始化

这可能需要大量工作,可以在顶层由<beans>统一控制延迟初始化行为

<beans default-lazy-init="true">...</beans>

4.3.4 继承

class SpecificFXNewsProvider extends FXNewsProvider{ private IFXNewsListener newsListener; private IFXNewsPersister newPersistener;...}

想让该子类与FXNewsProvider使用相同的IFXNewsPersister,那么可以使用如代码清单4-26所示的配置。

<bean id="superNewsProvider" class="..FXNewsProvider"> <property name="newsListener">  <ref bean="djNewsListener"/> </property> <property name="newPersistener">  <ref bean="djNewsPersister"/> </property></bean><bean id="subNewsProvider" parent="superNewsProvider" class="..SpecificFXNewsProvider"> <property name="newsListener">   <ref bean="specificNewsListener"/> </property></bean>

我们在声明subNewsProvider的时候,使用了parent属性,将其值指定为superNewsProvider,这样就继承了superNewsProvider定义的默认值,只需要将特定的属性进行更改,而不要全部又重新定义一遍。

parent属性还可以与abstract属性结合使用,达到将相应bean定义模板化的目的。

<bean id="newsProviderTemplate" abstract="true"> <property name="newPersistener">  <ref bean="djNewsPersister"/>  </property></bean><bean id="superNewsProvider" parent="newsProviderTemplate" class="..FXNewsProvider"> <property name="newsListener">  <ref bean="djNewsListener"/> </property></bean><bean id="subNewsProvider" parent="newsProviderTemplate" class="..SpecificFXNewsProvider"> <property name="newsListener">  <ref bean="specificNewsListener"/> </property> </bean>

newsProviderTemplate的bean定义通过abstract属性声明为true,说明这个bean定义不需要实例化。实际上,这就是之前提到的可以不指定class属性的少数场景之一。
容器在初始化对象实例的时候,不会关注将abstract属性声明为true的bean定义。如果你不想容器在初始化的时候实例化某些对象,那么可以将其abstract属性赋值true,以避免容器将其实例化。

原创粉丝点击