IOC Spring揭秘阅读总结

2.1 我们的理念是:让别人为你服务

        IoC是随着近年来轻量级容器(Lightweight Container)的兴起而逐渐被很多人提起的一个名词,它的全称为Inversion of Control,中文通常翻译为“控制反转”,它还有一个别名叫做依赖注入(Dependency Injection)。

        通常情况下,被注入对象会直接依赖于被依赖对象。但是,在IoC的场景中,二者之间通过IoC ServiceProvider来打交道,所有的被注入对象和依赖对象现在由IoC Service Provider统一管理。被注入对象需要什么,直接跟IoC Service Provider招呼一声,后者就会把相应的被依赖对象注入到被注入对象中,从而达到IoC Service Provider为被注入对象服务的目的。IoC Service Provider在这里就是通常的IoC容器所充当的角色。从被注入对象的角度看,与之前直接寻求依赖对象相比,依赖对象的取得方式发生了反转,控制也从被注入对象转到了IoC Service Provider那里。

2.2 手语,呼喊,还是心有灵犀

        IoC模式最权威的总结和解释,应该是Martin Fowler的那篇文章“Inversion of Control Containers and the Dependency Injection pattern”,其中提到了三种依赖注入的方式,即构造方法注入(constructorinjection)、setter方法注入(setter injection)以及接口注入(interface injection)。下面让我们详细看一下这三种方式的特点及其相互之间的差别。

2.2.1 构造方法注入

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

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

        构造方法注入方式比较直观,对象被构造完成后,即进入就绪状态,可以马上使用。这就好比你刚进酒吧的门,服务生已经将你喜欢的啤酒摆上了桌面一样。坐下就可马上享受一份清凉与惬意。

2.2.2 setter方法注入

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

        setter方法注入虽不像构造方法注入那样,让对象构造完成后即可使用,但相对来说更宽松一些,可以在对象构造完成后再注入。这就好比你可以到酒吧坐下后再决定要点什么啤酒,可以要百威,也可以要大雪,随意性比较强。如果你不急着喝,这种方式当然是最适合你的。

2.2.3 接口注入

        相对于前两种注入方式来说,接口注入没有那么简单明了。被注入对象如果想要IoC ServiceProvider为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。IoC Service Provider最终通过这些接口来了解应该为被注入对象注入什么依赖对象。

2.2.4 三种注入方式的比较

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

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

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

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

3.0 掌管大局的 Ioc Service Provider

3.1 IoC Service Provider 的职责

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

        业务对象的构建管理。在IoC场景中,业务对象无需关心所依赖的对象如何构建如何取得,但这部分工作始终需要有人来做。所以,IoC Service Provider需要将对象的构建逻辑从客户端对象那里剥离出来,以免这部分逻辑污染业务对象的实现。

        业务对象间的依赖绑定。对于IoC Service Provider来说,这个职责是最艰巨也是最重要的,这是它的最终使命之所在。如果不能完成这个职责,那么,无论业务对象如何的“呼喊”,也不会得到依赖对象的任何响应(最常见的倒是会收到一个NullPointerException)。IoC Service Provider通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象注入绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态。

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

        前面我们说过,被注入对象可以通过多种方式通知IoC Service Provider为其注入相应依赖。但问 3题在于,收到通知的IoC Service Provider是否就一定能够完全领会被注入对象的意图,并及时有效地为其提供想要的依赖呢?有些时候,事情可能并非像我们所想象的那样理所当然。当前流行的IoC Service Provider产品使用的注册对象管理信息的方式主要有以下几种。

3.2.1 直接编码方式

        当前大部分的IoC容器都应该支持直接编码方式,比如PicoContainer、Spring、Avalon等。在容器启动之前,我们就可以通过程序编码的方式将被注入对象和依赖对象注册到容器中,并明确它们相互之间的依赖注入关系。

3.2.2 配置文件方式

        这是一种较为普遍的依赖注入关系管理方式。像普通文本文件、properties文件、XML文件等,都可以成为管理依赖注入关系的载体。不过,最为常见的,还是通过XML文件来管理对象注册和对象间依赖关系,比如Spring IoC容器和在PicoContainer基础上扩展的NanoContainer,都是采用XML文件来管理和保存依赖注入信息的。

3.2.3 元数据方式

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

4. Spring的IoC容器之BeanFactory

        我们前面说过,Spring的IoC容器是一个IoC Service Provider,但是,这只是它被冠以IoC之名的部分原因,我们不能忽略的是“容器”。Spring的IoC容器是一个提供IoC支持的轻量级容器,除了基本的IoC支持,它作为轻量级容器还提供了IoC之外的支持。如在Spring的IoC容器之上,Spring还提供了相应的AOP框架支持、企业级服务集成等服务。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类型的容器是比较合适的选择。

        BeanFactory,顾名思义,就是生产Bean的工厂。当然,严格来说,这个“生产过程”可能不像说起来那么简单。既然Spring框架提倡使用POJO,那么把每个业务对象看作一个JavaBean对象,或许更容易理解为什么Spring的IoC基本容器会起这么一个名字。作为Spring提供的基本的IoC容器,BeanFactory可以完成作为IoC Service Provider的所有职责,包括业务对象的注册和对象间依赖关系的绑定。

        BeanFactory就像一个汽车生产厂。你从其他汽车零件厂商或者自己的零件生产部门取得汽车零件送入这个汽车生产厂,最后,只需要从生产线的终点取得成品汽车就可以了。相似地,将应用所需的所有业务对象交给BeanFactory之后,剩下要做的,就是直接从BeanFactory取得最终组装完成并且可用的对象。至于这个最终业务对象如何组装,你不需要关心,BeanFactory会帮你搞定。

        所以,对于客户端来说,与BeanFactory打交道其实很简单。最基本地,BeanFactory肯定会公开一个取得组装完成的对象的方法接口。

4.1 拥有BeanFactory之后的生活

        确切地说,拥有BeanFactory之后的生活没有太大的变化。当然,我的意思是看起来没有太大的变化。到底引入BeanFactory后的生活是什么样子,让我们一起来体验一下吧!

        依然“拉拉扯扯的事情”。对于应用程序的开发来说,不管是否引入BeanFactory之类的轻量级容器,应用的设计和开发流程实际上没有太大改变。换句话说,针对系统和业务逻辑,该如何设计和实现当前系统不受是否引入轻量级容器的影响。

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

        BeanFactory作为一个IoC Service Provider,为了能够明确管理各个业务对象以及业务对象之间的依赖绑定关系,同样需要某种途径来记录和管理这些信息。上一章在介绍IoC Service Provider时,我们提到通常会有三种方式来管理这些信息。而BeanFactory几乎支持所有这些方式,很令人兴奋,不是吗?

4.2.1 直接编码方式

        其实,把编码方式单独提出来称作一种方式并不十分恰当。因为不管什么方式,最终都需要编码才能“落实”所有信息并付诸使用。

        BeanFactory只是一个接口,我们最终需要一个该接口的实现来进行实际的Bean的管理,Default- ListableBeanFactory就是这么一个比较通用的BeanFactory实现类。DefaultListableBean-Factory除了间接地实现了BeanFactory接口,还实现了BeanDefinitionRegistry接口,该接口才是在BeanFactory的实现中担当Bean注册管理的角色。基本上,BeanFactory接口只定义如何访问容器内管理的Bean的方法,各个BeanFactory的具体实现类负责具体Bean的注册以及管理工作。BeanDefinitionRegistry接口定义抽象了Bean的注册逻辑。通常情况下,具体的BeanFactory实现类会实现这个接口来管理Bean的注册。


 4.2.2 外部配置文件方式

        Spring的IoC容器支持两种配置文件格式:Properties文件格式和XML文件格式。当然,如果你愿意也可以引入自己的文件格式,前提是真的需要。

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

1. Properties配置格式的加载

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

2. XML配置格式的加载

        XML配置格式是Spring支持最完整,功能最强大的表达方式。当然,一方面这得益于XML良好的语意表达能力;另一方面,就是Spring框架从开始就自始至终保持XML配置加载的统一性。同Properties配置加载类似,现在只不过是转而使用XML而已。Spring 2.x之前,XML配置文件采用DTD(DocumentType Definition)实现文档的格式约束。2.x之后,引入了基于XSD(XML Schema Definition)的约束方式。不过,原来的基于DTD的方式依然有效,因为从DTD转向XSD只是“形式”上的转变,所以,后面的大部分讲解还会沿用DTD的方式,只有必要时才会给出特殊说明。

4.2.3 注解方式

        可能你没有注意到,我在提到BeanFactory所支持的对象注册与依赖绑定方式的时候,说的是BeanFactory“几乎”支持IoC Service Provider可能使用的所有方式。之所以这么说,有两个原因。

 在Spring 2.5发布之前,Spring框架并没有正式支持基于注解方式的依赖注入;

 Spring 2.5发布的基于注解的依赖注入方式,如果不使用classpath-scanning功能的话,仍然部分依赖于“基于XML配置文件”的依赖注入方式。

        另外,注解是Java 5之后才引入的,所以,以下内容只适用于应用程序使用了Spring 2.5以及Java 5或者更高版本的情况之下。

        如果要通过注解标注的方式为FXNewsProvider注入所需要的依赖,现在可以使用@Autowired以及@Component对相关类进行标记。

4.3 BeanFactory的XML之旅

        XML格式的容器信息管理方式是Spring提供的最为强大、支持最为全面的方式。从Spring的参考文档到各Spring相关书籍,都是按照XML的配置进行说明的,这部分内容可以让你充分领略到Spring的IoC容器的魅力,以致于我们也不得不带你初次或者再次踏上Spring的XML之旅。

4.3.1 <beans>和<bean>

        所 有 使 用 XML 文件进行配置信息加载的Spring IoC 容器, 包括BeanFactory 和ApplicationContext的所有XML相应实现,都使用统一的XML格式。在Spring 2.0版本之前,这种格式由Spring提供的DTD规定,也就是说,所有的Spring容器加载的XML配置文件的头部,都需要以下形式的DOCTYPE声明:

        从Spring 2.0版本之后,Spring在继续保持向前兼容的前提下,既可以继续使用DTD方式的DOCTYPE进行配置文件格式的限定,又引入了基于XML Schema的文档声明。所以,Spring 2.0之后,同样可以使用代码清单4-11所展示的基于XSD的文档声明。


        不过,不管使用哪一种形式的文档声明,实际上限定的元素基本上是相同的。让我们从最顶层的元素开始,看一下这两种文档声明都限定了哪些元素吧!所有注册到容器的业务对象,在Spring中称之为Bean。所以,每一个对象在XML中的映射也自然而然地对应一个叫做的元素。既然容器最终可以管理所有的业务对象,那么在XML中把这些叫做的元素组织起来的,就叫做。多个组成一个很容易理解,不是吗?

1.<beans>之唯我独尊

        <beans>是XML配置文件中最顶层的元素,它下面可以包含0或者1个和多个<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。如果所管辖的按照某种规则,都有同样名称的初始化方法的话,可以在这里统一指定这个初始化方法名,而不用在每一个上都重复单独指定。

 default-destroy-method。与default-init-method相对应,如果所管辖的bean有按照某种规则使用了相同名称的对象销毁方法,可以通过这个属性统一指定。

2.<description>、<import>和<alias>

         之所以把这几个元素放到一起讲解,是因为通常情况下它们不是必需的。不过,了解一下也没什么不好,不是吗?

<description>

        可以通过在配置的文件中指定一些描述性的信息。通常情况下,该元素是省略的。当然,如果愿意,随时可以为我们效劳。

<import>

        通常情况下,可以根据模块功能或者层次关系,将配置信息分门别类地放到多个配置文件中。在 5想加载主要配置文件,并将主要配置文件所依赖的配置文件同时加载时,可以在这个主要的配置文件中通过元素对其所依赖的配置文件进行引用。比如,如果A.xml中的<bean>定义可能依赖B.xml中的某些<bean>定义,那么就可以在A.xml中使用<import>将B.xml引入到A.xml,以类似于<import resource="B.xml" />的形式。

        但是,这个功能在我看来价值不大,因为容器实际上可以同时加载多个配置,没有必要非通过一个配置文件来加载所有配置。不过,或许在有些场景中使用这种方式比较方便也说不定。

<alias>

        可以通过<alias>为某些<bean>起一些“外号”(别名),通常情况下是为了减少输入。比如,假设有个<bean>,它的名称为dataSourceForMasterDatabase,你可以为其添加一个<alias>,像这样<alias name = "dataSourceForMasterDatabase" alias = "masterDataSource"/>。以后通过dataSourceForMasterDatabase或者masterDataSource来引用这个<bean>都可以,只要你觉得方便就行。

4.3.2 孤孤单单一个人

       哦,错了,是孤孤单单一个Bean。每个业务对象作为个体,在Spring的XML配置文件中是与11元素一一对应的。窥一斑而知全豹,只要我们了解单个的业务对象是如何配置的,剩下的就可以“依葫芦画瓢”了。所以,让我们先从最简单的单一对象配置开始吧!如下代码演示了最基础的对象配置形式:

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

id属性

        通常,每个注册到容器的对象都需要一个唯一标志来将其与“同处一室”的“兄弟们”区分开来,就好像我们每一个人都有一个身份证号一样(重号的话就比较麻烦)。通过id属性来指定当前注册对象的beanName是什么。这里,通过id指定beanName为djNewsListener。实际上,并非任何情况下都需要指定每个<bean>的id,有些情况下,id可以省略,比如后面会提到的内部<bean>以及不需要根据beanName明确依赖关系的场合等。

        除了可以使用id来指定在容器中的标志,还可以使用name属性来指定<bean>的别(alias)。比如,以上定义,我们还可以像如下代码这样,为其添加别名:

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

class属性

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

个对象到底是何方神圣。在大部分情况下,该属性是必须的。仅在少数情况下不需要指定,如后面将提到的在使用抽象配置模板的情况下。

4.3.3 Help Me, Help You

        在大部分情况下,你不太可能选择单独“作战”,业务对象也是;各个业务对象之间会相互协作来更好地完成同一使命。这时,各个业务对象之间的相互依赖就是无法避免的。对象之间需要相互协作,在横向上它们存在一定的依赖性。而现在我们就是要看一下,在Spring的IoC容器的XML配置中,应该如何表达这种依赖性。

        既然业务对象现在都符合IoC的规则,那么要了解的表达方式其实也很简单,无非就是看一下构造方法注入和setter方法注入通过XML是如何表达的而已。那么,让我们开始吧!

1. 构造方法注入的XML之道

        按照Spring的IoC容器配置格式,要通过构造方法注入方式,为当前业务对象注入其所依赖的对象,需要使用。正常情况下,如以下代码所示:  

        对于元素,稍后会进行详细说明。这里你只需要知道,通过这个元素来指明容器将为djNewsProvider这个注入通过所引用的Bean实例。这种方式可能看起来或者编写起来不是很简洁,最新版本的Spring也支持配置简写形式,如以下代码所示:

        简洁多了不是嘛?其实,无非就是表达方式不同而已,实际达到的效果是一样的。有些时候,容器在加载XML配置的时候,因为某些原因,无法明确配置项与对象的构造方法参数列表的一一对应关系,就需要请的type或者index属性出马。比如,对象存在多个构造方法,当参数列表数目相同而类型不同的时候,容器无法区分应该使用哪个构造方法来实例化对象,或者构造方法可能同时传入最少两个类型相同的对象。

type属性

index属性

        当某个业务对象的构造方法同时传入了多个类型相同的参数时,Spring又该如何将这些配置中的信息与实际对象的参数一一对应呢?好在,如果配置项信息和对象参数可以按照顺序初步对应的话, Spring还是可以正常工作的

2. setter方法注入的XML之道

        与构造方法注入可以使用注入配置相对应,Spring为setter方法注入提供了<property>元素。

3.<property>和<constructor-arg>中可用的配置项

        之前我们看到,可以通过在和这两个元素内部嵌套或者,来指定将为当前对象注入的简单数据类型或者某个对象的引用。不过,为了能够指定多种注入类型,Spring还提供了其他的元素供我们使用,这包括bean、ref、idref、value、null、list、set、map、props。下面我们来逐个详细讲述它们。

(1)可以通过value为主体对象注入简单的数据类型,不但可以指定String类型的数据,而且可以指定其他Java语言中的原始类型以及它们的包装器(wrapper)类型,比如int、Integer等。容器在注入的时候,会做适当的转换工作(我们会在后面揭示转换的奥秘)。

(2)使用ref来引用容器中其他的对象实例,可以通过ref的local、parent和bean属性来指定引用的对象的beanName是什么。

local、parent和bean的区别在于:

 local只能指定与当前配置的对象在同一个配置文件的对象定义的名称(可以获得XML解析器的id约束验证支持);

 parent则只能指定位于当前容器的父容器中定义的对象引用;

 bean则基本上通吃,所以,通常情况下,直接使用bean来指定对象引用就可以了。

(3)<idref>。如果要为当前对象注入所依赖的对象的名称,而不是引用,那么通常情况下,可以 使用来达到这个目的

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

5)<list>对应注入对象类型为java.util.List及其子类或者数组类型的依赖对象。

4. depends-on

        通常情况下,可以直接通过之前提到的所有元素,来显式地指定bean之间的依赖关系。这样,容器在初始化当前bean定义的时候,会根据这些元素所标记的依赖关系,首先实例化当前bean定义所依赖的其他bean定义。但是,如果某些时候,我们没有通过类似的元素明确指定对象A依赖于对象B的话,如何让容器在实例化对象A之前首先实例化对象B呢?

考虑以下所示代码:

        系统中所有需要日志记录的类,都需要在这些类使用之前首先初始化log4j。那么,就会非显式地依赖于SystemConfigurationSetup的静态初始化块。如果ClassA需要使用log4j,那么就必须在bean定义中使用depends-on来要求容器在初始化自身实例之前首先实例化SystemConfigurationSetup,以保证日志系统的可用,如下代码演示的正是这种情况:

5. autowire

        除了可以通过配置明确指定bean之间的依赖关系,Spirng还提供了根据bean定义的某些特点将相互依赖的某些bean直接自动绑定的功能。通过的autowire属性,可以指定当前bean定义采用某种类型的自动绑定模式。这样,你就无需手工明确指定该bean定义相关的依赖关系,从而也可以免去一些手工输入的工作量。

        Spring提供了5种自动绑定模式,即no、byName、byType、constructor和autodetect,下面是它们的具体介绍。

 no

        容器默认的自动绑定模式,也就是不采用任何形式的自动绑定,完全依赖手工明确配置各个bean之间的依赖关系

 byName

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


        需要注意两点:第一,我们并没有明确指定fooBean的依赖关系,而仅指定了它的autowire属性为byName;第二,第二个bean定义的id为emphasisAttribute,与Foo类中的实例变量名称相同。

 byType

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

        对于byName模式中的实例类Foo来说,容器会在其所管理的所有bean定义中寻找类型为Bar的bean定义。如果找到,则将找到的bean绑定到Foo的bean定义;如果没有找到,则不做设置。但如果找到多个,容器会告诉你它解决不了“该选用哪一个”的问题,你只好自己查找原因,并自己修正该问题。所以,byType只能保证,在容器中只存在一个符合条件的依赖对象的时候才会发挥最大的作用,如果容器中存在多个相同类型的bean定义,那么,不好意思,采用手动明确配置吧!

        指定byType类型的autowire模式与byName没什么差别,只是autowire的值换成byType而已。

 constructor

        byName和byType类型的自动绑定模式是针对property的自动绑定,而constructor类型则是针对构造方法参数的类型而进行的自动绑定,它同样是byType类型的绑定模式。不过,constructor是匹配构造方法的参数类型,而不是实例属性的类型。与byType模式类似,如果找到不止一个符合条件的bean定义,那么,容器会返回错误。使用上也与byType没有太大差别,只不过是应用到需要使用构造方法注入的bean定义之上,代码清单4-23给出了一个使用construtor模式进行自动绑定的简单场景演示。

        
 autodetect

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

自动绑定和手动明确绑定各有利弊。自动绑定的优点有如下两点。

(1) 某种程度上可以有效减少手动敲入配置信息的工作量。

(2) 某些情况下,即使为当前对象增加了新的依赖关系,但只要容器中存在相应的依赖对象,就不需要更改任何配置信息。

自动绑定的缺点有如下几点。

(1) 自动绑定不如明确依赖关系一目了然。我们可以根据明确的依赖关系对整个系统有一个明确的认识,但使用自动绑定的话,就可能需要在类定义以及配置文件之间,甚至各个配置文件之间来回转换以取得相应的信息。

(2) 某些情况下,自动绑定无法满足系统需要,甚至导致系统行为异常或者不可预知。根据类型(byType)匹配进行的自动绑定,如果系统中增加了另一个相同类型的bean定义,那么整个系统就会崩溃;根据名字(byName)匹配进行的自动绑定,如果把原来系统中相同名称的bean定义类型给换掉,就会造成问题,而这些可能都是在不经意间发生的。

(3) 使用自动绑定,我们可能无法获得某些工具的良好支持,比如Spring IDE。

6. dependency-check

        我们可以使用每个的dependency-check属性对其所依赖的对象进行最终检查该功能主要与自动绑定结合使用,可以保证当自动绑定完成后,最终确认每个对象所依赖的对象是否按照所预期的那样被注入。当然,并不是说不可以与平常的明确绑定方式一起使用。

7. lazy-init

        延迟初始化(lazy-init)这个特性的作用,主要是可以针对ApplicationContext容器的bean初始化行为施以更多控制。与BeanFactory不同,ApplicationContext在容器启动的时候,就会马上对所有的“singleton的bean定义”①进行实例化操作。通常这种默认行为是好的,因为如果系统有问题的话,可以在第一时间发现这些问题,但有时,我们不想某些bean定义在容器启动后就直接实例化,可能出于容器启动时间的考虑,也可能出于其他原因的考虑。总之,我们想改变某个或者某些bean定义在ApplicationContext容器中的默认实例化时机。这时,就可以通过的lazy-init属性来控制这种初始化行为.

4.3.4 继承?我也会!

        使用parent属性,只需要将特定的属性进行更改,而不要全部又重新定义一遍。

4.3.5 bean的 scope

        多数中文资料在讲解bean的scope时喜欢用“作用域”这个名词,scope用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象。

        Spring容器最初提供了两种bean的scope类型:singleton和prototype,但发布2.0之后,又引入了另外三种scope类型,即request、session和global session类型。不过这三种类型有所限制,只能在Web应用中使用。也就是说,只有在支持Web应用的ApplicationContext中使用这三个scope才是合理的。

1. singleton

        配置中的bean定义可以看作是一个模板,容器会根据这个模板来构造对象。但是要根据这个模板构造多少对象实例,又该让这些构造完的对象实例存活多久,则由容器根据bean定义的scope语意来决定。标记为拥有singleton scope的对象定义,在Spring的IoC容器中只存在一个实例,所有对该对象的引用将共享这个实例。该实例从容器启动,并因为第一次被请求而初始化之后,将一直存活到容器退出,也就是说,它与IoC容器“几乎”拥有相同的“寿命”。

可以从两个方面来看待singleton的bean所具有的特性。

 对象实例数量。singleton类型的bean定义,在一个容器中只存在一个共享实例,所有对该类型bean的依赖都引用这一单一实例。这就好像每个幼儿园都会有一个滑梯一样,这个幼儿园的小朋友共同使用这一个滑梯。而对于该幼儿园容器来说,滑梯实际上就是一个singleton的bean。

 对象存活时间。singleton类型bean定义,从容器启动,到它第一次被请求而实例化开始,只要容器不销毁或者退出,该类型bean的单一实例就会一直存活。

        通常情况下,如果你不指定bean的scope,singleton便是容器默认的scope,所以,下面三种配置形式实际上达成的是同样的效果:

2. prototype

        针对声明为拥有prototype scope的bean定义,容器在接到该类型对象的请求的时候,会每次都重新生成一个新的对象实例给请求方。虽然这种类型的对象的实例化以及属性设置等工作都是由容器负责的,但是只要准备完毕,并且对象实例返回给请求方之后,容器就不再拥有当前返回对象的引用,请求方需要自己负责当前返回对象的后继生命周期的管理工作,包括该对象的销毁。也就是说,容器每次返回给请求方一个新的对象实例之后,就任由这个对象实例“自生自灭”了。

       对于那些请求方不能共享使用的对象类型,应该将其bean定义的scope设置为prototype。这样,每个请求方可以得到自己对应的一个对象实例,而不会出现上面“哭鼻子”的现象。通常,声明为prototype的scope的bean定义类型,都是一些有状态的,比如保存每个顾客信息的对象

3. request、session和global session

        这三个scope类型是Spirng 2.0之后新增加的,它们不像之前的singleton和prototype那么“通用”,因为它们只适用于Web应用程序,通常是与XmlWebApplicationContext共同使用,而这些将在第6部分详细讨论。不过,既然它们也属于scope的概念,这里就简单提几句。

 request

        Spring容器,即XmlWebApplicationContext会为每个HTTP请求创建一个全新的Request-Processor对象供当前请求使用,当请求结束后,该对象实例的生命周期即告结束。当同时有10个HTTP请求进来的时候,容器会分别针对这10个请求返回10个全新的RequestProcessor对象实例,且它们之间互不干扰。从不是很严格的意义上说,request可以看作prototype的一种特例,除了场景更加具体之外,语意上差不多。

 session

        对于Web应用来说,放到session中的最普遍的信息就是用户的登录信息,对于这种放到session中的信息,我们可使用如下形式指定其scope为session:

        Spring容器会为每个独立的session创建属于它们自己的全新的UserPreferences对象实例。与request相比,除了拥有session scope的bean的实例具有比request scope的bean可能更长的存活时间,其他方面真是没什么差别。

 global session

        还是userPreferences,不过scope对应的值换一下。global session只有应用在基于portlet的Web应用程序中才有意义,它映射到portlet的global范围的 session。如果在普通的基于servlet的Web应用中使用了这个类型的scope,容器会将其作为普通的session类型的scope对待。

4. 自定义scope类型

        在Spring 2.0之后的版本中,容器提供了对scope的扩展点,这样,你可以根据自己的需要或者应用的场景,来添加自定义的scope类型。需要说明的是,默认的singleton和prototype是硬编码到代码中的,而request、session和global session,包括自定义scope类型,则属于可扩展的scope行列,它们都实现了rg.springframework.beans.factory.config.Scope接口。

4.3.6 工厂方法与FactoryBean

        在强调“面向接口编程”的同时,有一点需要注意:虽然对象可以通过声明接口来避免对特定接口实现类的过度耦合,但总归需要一种方式将声明依赖接口的对象与接口实现类关联起来。否则,只依赖一个不做任何事情的接口是没有任何用处的。

        通常的做法是通过使用工厂方法(Factory Method)模式,提供一个工厂类来实例化具体的接口实现类,这样,主体对象只需要依赖工厂类,具体使用的实现类有变更的话,只是变更工厂类,而主体对象不需要做任何变动。

        针对使用工厂方法模式实例化对象的方式,Spring的IoC容器同样提供了对应的集成支持。我们所要做的,只是将工厂类所返回的具体的接口实现类注入给主体对象(这里是Foo)。

1. 静态工厂方法(Static Factory Method)        

2. 非静态工厂方法(Instance Factory Method)

3. FactoryBean

        FactoryBean是Spring容器提供的一种可以扩展容器对象实例化逻辑的接口,请不要将其与容器名称BeanFactory相混淆。FactoryBean,其主语是Bean,定语为Factory,也就是说,它本身与其他注册到容器的对象一样,只是一个Bean而已,只不过,这种类型的Bean本身就是生产对象的工厂(Factory)。

        当某些对象的实例化过程过于烦琐,通过XML配置过于复杂,使我们宁愿使用Java代码来完成这个实例化过程的时候,或者,某些第三方库不能直接注册到Spring容器的时候,就可以实现org.springframework.beans.factory.FactoryBean接口,给出自己的对象实例化逻辑代码。当然,不使用FactoryBean,而像通常那样实现自定义的工厂方法类也是可以的。不过,FactoryBean可是Spring提供的对付这种情况的“制式装备”哦!

4.3.7 偷梁换柱之术

        在学习以下内容之前,先提一下有关bean的scope的使用“陷阱”,特别是prototype在容器中的使用,以此引出本节将要介绍的Spring容器较为独特的功能特性:方法注入(Method Injection)以及方法替换(Method Replacement)。

        我们知道,拥有prototype类型scope的bean,在请求方每次向容器请求该类型对象的时候,容器都会返回一个全新的该对象实例。为了简化问题的叙述,我们直接将FX News系统中的FXNewsBean定义注册到容器中,并将其scope设置为prototype。因为它是有状态的类型,每条新闻都应该是新的独立个体;同时,我们给出MockNewsPersister类,使其实现IFXNewsPersister接口,以模拟注入FXNewsBean实例后的情况。这样,我们就有了代码清单4-35所展示的类声明和相关配置。

  从输出看,对象实例是相同的,而这与我们的初衷是相悖的。因为每次调用persistNews都会调用getNewsBean()方法并返回一个FXNewsBean实例,而FXNewsBean实例是prototype类型的,因此每次不是应该输出不同的对象实例嘛?

        好了,问题实际上不是出在FXNewsBean的scope类型是否是prototype的,而是出在实例的取得方式上面。虽然FXNewsBean拥有prototype类型的scope,但当容器将一个FXNewsBean的实例注入MockNewsPersister之后,MockNewsPersister就会一直持有这个FXNewsBean实例的引用。虽然每次输出都调用了getNewsBean()方法并返回了FXNewsBean 的实例,但实际上每次返回的都是MockNewsPersister持有的容器第一次注入的实例。这就是问题之所在。换句话说,第一个实例注入后,MockNewsPersister再也没有重新向容器申请新的实例。所以,容器也不会重新为其注入新的FXNewsBean类型的实例。知道原因之后,我们就可以解决这个问题了。解决问题的关键在于保证getNewsBean()方法每次从容器中取得新的FXNewsBean实例,而不是每次都返回其持有的单一实例。

1. 方法注入

        Spring容器提出了一种叫做方法注入(Method Injection)的方式,可以帮助我们解决上述问题。我们所要做的很简单,只要让getNewsBean方法声明符合规定的格式,并在配置文件中通知容器,当该方法被调用的时候,每次返回指定类型的对象实例即可。方法声明需要符合的规格定义如下:

        也就是说,该方法必须能够被子类实现或者覆写,因为容器会为我们要进行方法注入的对象使用Cglib动态生成一个子类实现,从而替代当前对象。既然我们的getNewsBean()方法已经满足以上方法声明格式,剩下唯一要做的就是配置该类,配置内容如下所示:


        通过的name属性指定需要注入的方法名,bean属性指定需要注入的对象,当getNewsBean方法被调用的时候,容器可以每次返回一个新的FXNewsBean类型的实例。

2. 殊途同归        

        除了使用方法注入来达到“每次调用都让容器返回新的对象实例”的目的,还可以使用其他方式达到相同的目的。下面给出其他两种解决类似问题的方法,供读者参考。

 使用BeanFactoryAware接口我们知道,即使没有方法注入,只要在实现getNewsBean()方法的时候,能够保证每次调用Bean- Factory的getBean("newsBean"),就同样可以每次都取得新的FXNewsBean对象实例。现在,我们唯一需要的,就是让MockNewsPersister拥有一个BeanFactory的引用。

        Spring框架提供了一个BeanFactoryAware接口,容器在实例化实现了该接口的bean定义的过程中,会自动将容器本身注入该bean。这样,该bean就持有了它所处的BeanFactory的引用。

 使用ObjectFactoryCreatingFactoryBean

        ObjectFactoryCreatingFactoryBean是Spring 提供的一个FactoryBean实现,它返回一个ObjectFactory实例。从ObjectFactoryCreatingFactoryBean返回的这个ObjectFactory实例可以为我们返回容器管理的相关对象。实际上, ObjectFactoryCreatingFactoryBean 实现了BeanFactoryAware接口,它返回的ObjectFactory实例只是特定于与Spring容器进行交互的一个实现而已。使用它的好处就是,隔离了客户端对象对BeanFactory的直接引用。

3. 方法替换

        与方法注入只是通过相应方法为主体对象注入依赖对象不同,方法替换更多体现在方法的实现层面上,它可以灵活替换或者说以新的方法实现覆盖掉原来某个方法的实现逻辑。基本上可以认为,方法替换可以帮助我们实现简单的方法拦截功能。要知道,我们现在可是在不知不觉中迈上了AOP的大道哦!

4.4 容器背后的秘密        

        子曰:学而不思则罔。除了了解Spring的IoC容器如何使用,了解Spring的IoC容器都提供了哪些功能,我们也应该想一下,Spring的IoC容器内部到底是如何来实现这些的呢?虽然我们不太可能“重新发明轮子”,但是,如图4-7(该图摘自Spring官方参考文档)所示的那样,只告诉你“Magic Happens Here”,你是否就能心满意足呢?

4.4.1 “战略性观望”

        Spring的IoC容器所起的作用,就像图4-7所展示的那样,它会以某种方式加载ConfigurationMetadata(通常也就是XML格式的配置信息),然后根据这些信息绑定整个系统的对象,最终组装成一个可用的基于轻量级容器的应用系统。

        Spring的IoC容器实现以上功能的过程,基本上可以按照类似的流程划分为两个阶段,即容器启动 阶段和Bean实例化阶段,如图4-8所示。

        Spring的IoC容器在实现的时候,充分运用了这两个实现阶段的不同特点,在每个阶段都加入了相应的容器扩展点,以便我们可以根据具体场景的需要加入自定义的扩展逻辑。


1. 容器启动阶段

        容器启动伊始,首先会通过某种途径加载Configuration MetaData。除了代码方式比较直接,在大部分情况下,容器需要依赖某些工具类(BeanDefinitionReader)对加载的Configuration MetaData进行解析和分析,并将分析后的信息编组为相应的BeanDefinition,最后把这些保存了bean定义必要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器启动工作就完成了。图4-9演示了这个阶段的主要工作。

        总地来说,该阶段所做的工作可以认为是准备性的,重点更加侧重于对象管理信息的收集。当然,一些验证性或者辅助性的工作也可以在这个阶段完成。

2. Bean实例化阶段

        经过第一阶段,现在所有的bean定义信息都通过BeanDefinition的方式注册到了BeanDefinitionRegistry中。当某个请求方通过容器的getBean方法明确地请求某个对象,或者因依赖关系容器需要隐式地调用getBean方法时,就会触发第二阶段的活动。

        该阶段,容器会首先检查所请求的对象之前是否已经初始化。如果没有,则会根据注册的BeanDefinition所提供的信息实例化被请求对象,并为其注入依赖。如果该对象实现了某些回调接口,也会根据回调接口的要求来装配它。当该对象装配完毕之后,容器会立即将其返回请求方使用。如果说第一阶段只是根据图纸装配生产线的话,那么第二阶段就是使用装配好的生产线来生产具体的产品了。

4.4.2 插手“容器的启动”

        Spring提供了一种叫做BeanFactoryPostProcessor的容器扩展机制。该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做相应的修改。这就相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作,比如修改其中bean定义的某些属性,为bean定义增加其他信息等。

        如果要自定义实现BeanFactoryPostProcessor,通常我们需要实现org.springframework.beans.factory.config.BeanFactoryPostProcessor接口。同时,因为一个容器可能拥有多个Bean-FactoryPostProcessor,这个时候可能需要实现类同时实现Spring的org.springframework.core.Ordered接口,以保证各个BeanFactoryPostProcessor可以按照预先设定的顺序执行(如果顺序紧要的话)。但是,因为Spring已经提供了几个现成的BeanFactoryPostProcessor实现类,所以,大多时候,我们很少自己去实现某个BeanFactoryPostProcessor。其中,org.springframework.beans.factory.config.PropertyPlaceholderConfigurer和org.springframework.beans.factory.config.Property OverrideConfigurer是两个比较常用的BeanFactoryPostProcessor。另外,为了处理配置文件中的数据类型与真正的业务对象所定义的数据类型转换,Spring还允许我们通过org.springframework.beans.factory.config.CustomEditorConfigurer来注册自定义的PropertyEditor以补助容器中默认的PropertyEditor。可以参考BeanFactoryPostProcessor的Javadoc来了解更多其实现子类的情况。

        我 们 可 以 通 过两种方式来应用BeanFactoryPostProcessor , 分别针对基本的IoC 容器BeanFactory和较为先进的容器ApplicationContext。

        对于BeanFactory来说,我们需要用手动方式应用所有的BeanFactoryPostProcessor

        对于ApplicationContext来说,情况看起来要好得多。因为ApplicationContext会自动识别配置文件中的BeanFactoryPostProcessor并应用它,所以,相对于BeanFactory,在ApplicationContext中加载并应用BeanFactoryPostProcessor,仅需要在XML配置文件中将这些BeanFactoryPost-Processor简单配置一下即可。

下面让我们看一下Spring提供的这几个BeanFactoryPostProcessor实现都可以完成什么功能。

1. PropertyPlaceholderConfigurer 

        通常情况下,我们不想将类似于系统管理相关的信息同业务对象相关的配置信息混杂到XML配置文件中,以免部署或者维护期间因为改动繁杂的XML配置文件而出现问题。我们会将一些数据库连接信息、邮件服务器等相关信息单独配置到一个properties文件中,这样,如果因系统资源变动的话,只需要关注这些简单properties配置文件即可。

        PropertyPlaceholderConfigurer允许我们在XML配置文件中使用占位符(PlaceHolder),并将这些占位符所代表的资源单独配置到简单的properties文件中来加载。

2. PropertyOverrideConfigurer

        PropertyPlaceholderConfigurer可以通过占位符,来明确表明bean定义中的property与 2properties文件中的各配置项之间的对应关系。如果说PropertyPlaceholderConfigurer做的这些是“明事”的话,那相对来说,PropertyOverrideConfigurer所做的可能就有点儿“神不知鬼不觉”了。

        可以通过PropertyOverrideConfigurer对容器中配置的任何你想处理的bean定义的property信息进行覆盖替换。这听起来比较抽象,我们还是给个例子吧!比如之前的dataSource定义中,maxActive的值为100,如果我们觉得100不合适,那么可以通过PropertyOverrideConfigurer在其相应的properties文件中做如下所示配置,把100这个值给覆盖掉,如将其配置为200:

dataSource.maxActive=200

        这样,当容器实例化对象的时候,该dataSource对象对应的maxActive值就是200,而不是原来 XML配置中的100。也就是说,PropertyOverrideConfigurer的properties文件中的配置项,覆盖掉了原来XML中的bean定义的property信息。但这样的活动,只看XML配置的话,你根本看不出哪个bean定义的哪个property会被覆盖替换掉,只有查看PropertyOverrideConfigurer指定的properties配置文件才会了解。基本上,这种覆盖替换对于bean定义来说是透明的。

3. CustomEditorConfigurer

          其他两个BeanFactoryPostProcessor都是通过对BeanDefinition中的数据进行变更以达到某  种目的。与它们有所不同,CustomEditorConfigurer是另一种类型的BeanFactoryPostProcessor实现,它只是辅助性地将后期会用到的信息注册到容器,对BeanDefinition没有做任何变动。

        我们知道,不管对象是什么类型,也不管这些对象所声明的依赖对象是什么类型,通常都是通过XML(或者properties甚至其他媒介)文件格式来配置这些对象类型。但XML所记载的,都是String类型,即容器从XML格式的文件中读取的都是字符串形式,最终应用程序却是由各种类型的对象所构成。要想完成这种由字符串到具体对象的转换(不管这个转换工作最终由谁来做),都需要这种转换规则相关的信息,而CustomEditorConfigurer就是帮助我们传达类似信息的。

 自定义PropertyEditor

4.4.3 了解Bean的一生

        容器启动之后,并不会马上就实例化相应的bean定义。我们知道,容器现在仅仅拥有所有对象的BeanDefinition来保存实例化阶段将要用的必要信息。只有当请求方通过BeanFactory的getBean()方法来请求某个对象实例的时候,才有可能触发Bean实例化阶段的活动。BeanFactory的getBe法可以被客户端对象显式调用,也可以在容器内部隐式地被调用。隐式调用有如下两种情况。

 对于BeanFactory来说,对象实例化默认采用延迟初始化。通常情况下,当对象A被请求而需要第一次实例化的时候,如果它所依赖的对象B之前同样没有被实例化,那么容器会先实例化对象A所依赖的对象。这时容器内部就会首先实例化对象B,以及对象 A依赖的其他还没有实例化的对象。这种情况是容器内部调用getBean(),对于本次请求的请求方是隐式的。

 ApplicationContext启动之后会实例化所有的bean定义,这个特性在本书中已经多次提到。但ApplicationContext在实现的过程中依然遵循Spring容器实现流程的两个阶段,只不过它会在启动阶段的活动完成之后,紧接着调用注册到该容器的所有bean定义的实例化方法getBean()。这就是为什么当你得到ApplicationContext类型的容器引用时,容器内所有对象已经被全部实例化完成。不信你查一下类org.AbstractApplicationContext的refresh()方法。

        之所以说getBean()方法是有可能触发Bean实例化阶段的活动,是因为只有当对应某个bean定义的getBean()方法第一次被调用时,不管是显式的还是隐式的,Bean实例化阶段的活动才会被触发,第二次被调用则会直接返回容器缓存的第一次实例化完的对象实例(prototype类型bean除外)。当getBean()方法内部发现该bean定义之前还没有被实例化之后,会通过createBean()方法来进行具体的对象实例化,实例化过程如图4-10所示

        Spring容器将对其所管理的对象全部给予统一的生命周期管理,这些被管理的对象完全摆脱了原来那种“new完后被使用,脱离作用域后即被回收”的命运。下面我们将详细看一看现在的每个bean在容器中是如何走过其一生的。

1. Bean的实例化与BeanWrapper

        容器在内部实现的时候,采用“策略模式(Strategy Pattern)”来决定采用何种方式初始化bean实例。通常,可以通过反射或者CGLIB动态字节码生成来初始化相应的bean实例或者动态生成其子类。

        org.springframework.beans.factory.support.InstantiationStrategy定义是实例化策略的抽象接口,其直接子类SimpleInstantiationStrategy实现了简单的对象实例化功能,可以通过反射来实例化对象实例,但不支持方法注入方式的对象实例化。CglibSubclassingInstantiation-Strategy继承了SimpleInstantiationStrategy的以反射方式实例化对象的功能,并且通过CGLIB的动态字节码生成功能,该策略实现类可以动态生成某个类的子类,进而满足了方法注入所需的对象实例化需求。默认情况下,容器内部采用的是CglibSubclassingInstantiationStrategy。

        容器只要根据相应bean定义的BeanDefintion取得实例化信息,结合CglibSubclassingInstantiationStrategy以及不同的bean定义类型,就可以返回实例化完成的对象实例。但是,返回方式上有些“点缀”。不是直接返回构造完成的对象实例,而是以BeanWrapper对构造完成的对象实例进行包裹,返回相应的BeanWrapper实例。至此,第一步结束。

        BeanWrapper接口通常在Spring框架内部使用,它有一个实现类org.springframework.beans.BeanWrapperImpl。其作用是对某个bean进行“包裹”,然后对这个“包裹”的bean进行操作,比如设置或者获取bean的相应属性值。而在第一步结束后返回BeanWrapper实例而不是原先的对象实例,就是为了第二步“设置对象属性”。

        BeanWrapper定义继承了org.springframework.beans.PropertyAccessor接口,可以以统一的方式对对象属性进行访问;BeanWrapper定义同时又直接或者间接继承了PropertyEditorRegistry和TypeConverter接口。不知你是否还记得CustomEditorConfigurer?当把各种PropertyEditor注册给容器时,知道后面谁用到这些PropertyEditor吗?对,就是BeanWrapper!在第一步构造完成对象之后,Spring会根据对象实例构造一个BeanWrapperImpl实例,然后将之前CustomEditor-Configurer注册的PropertyEditor复制一份给BeanWrapperImpl实例(这就是BeanWrapper同时又是PropertyEditorRegistry的原因)。这样,当BeanWrapper转换类型、设置对象属性值时,就不会无从下手了。

2. 各色的Aware接口

        当对象实例化完成并且相关属性以及依赖设置完成之后,Spring容器会检查当前对象实例是否实现了一系列的以Aware命名结尾的接口定义。如果是,则将这些Aware接口定义中规定的依赖注入给当前对象实例。

3. BeanPostProcessor

        BeanPostProcessor的概念容易与BeanFactoryPostProcessor的概念混淆。但只要记住Bean-PostProcessor是存在于对象实例化阶段,而BeanFactoryPostProcessor则是存在于容器启动阶段,这两个概念就比较容易区分了。与BeanFactoryPostProcessor通常会处理容器内所有符合条件的BeanDefinition类似,Bean-PostProcessor会处理容器内所有符合条件的实例化后的对象实例。

4. InitializingBean和init-method

        org.springframework.beans.factory.InitializingBean是容器内部广泛使用的一个对象生命周期标识接口。

        该接口定义很简单,其作用在于,在对象实例化过程调用过“BeanPostProcessor的前置处理”之后,会接着检测当前对象是否实现了InitializingBean接口,如果是,则会调用其afterPropertiesSet()方法进一步调整对象实例的状态。比如,在有些情况下,某个业务对象实例化完成后,还不能处于可以使用状态。这个时候就可以让该业务对象实现该接口,并在方法afterPropertiesSet()中完成对该业务对象的后续处理。

5. DisposableBean与destroy-method

        当所有的一切,该设置的设置,该注入的注入,该调用的调用完成之后,容器将检查singleton类型的bean实例,看其是否实现了org.springframework.beans.factory.DisposableBean接口。或者其对应的bean定义是否通过的destroy-method属性指定了自定义的对象销毁方法。如果是,就会为该实例注册一个用于对象销毁的回调(Callback),以便在这些singleton类型的对象实例销毁之前,执行销毁逻辑。

        与InitializingBean和init-method用于对象的自定义初始化相对应,DisposableBean和destroy-method为对象提供了执行自定义销毁逻辑的机会。

推荐阅读更多精彩内容