Spring2.x——Spring的Ioc容器之BeanFactory

目录

1、Spring的Ioc容器之BeanFactory

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

1.1.1、直接编码方式

1.1.2、外部配置文件方式

1.1.3、注解方式

1.2、BeanFactory的XML之旅

1.2.1、单一对象配置

1.2.2、XML表达构造方法注入和setter方法注入

1.2.3、继承

 1.2.4、bean的scope

1.2.5、工厂方法与 FactoryBean

1.2.6、方法注入

1.2.7、方法替换

1.3、容器探索

1.3.1、容器启动过程

1.3.2、容器扩展

1.3.3、了解bean的一生


1、Spring的Ioc容器之BeanFactory

Spring提供了两种容器类型:BeanFactory和ApplicationContext

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

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

1.1.1、直接编码方式

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

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

1.1.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("配置文件路径");
// 现在我们就取得了一个可用的BeanDefinitionRegistry实例

1、Properties配置格式的加载

Spring提供了 org.springframework.beans.factory.support.PropertiesBeanDefinitionReader 类用于Properties格式配置文件的加载。

2、XML配置格式的加载

1.1.3、注解方式

只适用于应用程序使用了Spring2.5以及Java5或者更高版本的情况之下。

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

1.2、BeanFactory的XML之旅

所有使用XML文件进行配置信息加载的Spring IoC容器,包括BeanFactory和ApplicationContext的所有XML相应实现,都使用统一的XML格式。在Spring2.0版本之前,这种格式由Spring提供的DTD规定。

从Spring2.0版本之后,Spring在继续保持向前兼容的前提下,既可以继续使用DTD方式的DOCTYPE进行配置文件格式的限定,有引入基于XML Schema的文档声明。

1、Beans

<beans>是XML配置文件中最顶层的元素,它下面可以包含0或者1个<description>和多个<bean>以及<import>或者<alias>

<beans>有以下属性(attribute)对所辖的<bean>进行统一的默认行为设置:

  • default-lazy-init。其值可以指定为true或者false,默认值为false。用来标志是否对所有的<bean>进行延迟初始化。
  • 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>。 <import> 元素对其所依赖的配置文件进行引用。比如,如果A.xml中的<bean>定义可能依赖B.xml中的某些 <bean> 定义,那么就可以在A.xml中使用 <import> 将B.xml引入到A.xml,以类似于
    <import resource="B.xml"/> 的形式。
  • <alias>。可以通过 <alias> 为某些 <bean> 起一些“外号”(别名),通常情况下是为了减少输入。

1.2.1、单一对象配置

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

1、id 属性

通过 id 属性来指定当前注册对象的 beanName 是什么。

除了可以使用 id 来指定 <bean> 在容器中的标志,还可以使用 name 属性来指定 <bean> 的别名(alias)。

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

</bean>

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

2、class 属性

每个注册到容器的对象都需要通过 <bean> 元素的 class 属性指定其类型,否则,容器可不知道这个对象到底是何方神圣。在大部分情况下,该属性是必须的。

1.2.2、XML表达构造方法注入和setter方法注入

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

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

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

 最新版本的Spring也支持配置简写形式,如以下代码所示:

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

当对象存在多个构造方法,当参数列表数目相同而类型不同的时候,容器无法区分应该使用哪个构造方法来实例化对象,或者构造方法可能同时传入最少两个类型相同的对象。

  • type属性。可以使用 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 bean="djNewsListener"/>
    </property>
    <property name="newPersistener">
        <ref bean="djNewsPersister"/>
    </property>
</bean>
<bean id="djNewsProvider" class="..FXNewsProvider">
    <property name="newsListener" ref="djNewsListener"/>
    <property name="newPersistener" ref="djNewsPersister"/>
</bean>

如果只是使用 <property> 进行依赖注入的话,请确保你的对象提供了默认的构造方法,也就是一个参数都没有的那个。 

3、<property>和<construct-arg>中可用的配置项

为了能够指定多种注入类型,Spring还提供了其他的元素供我们使用,这包括bean、ref、idref、value、null、list、set、map、props。

(1)<value>

可以通过 value 为主体对象注入简单的数据类型,不但可以指定 String 类型的数据,而且可以指定其他Java语言中的原始类型以及它们的包装器(wrapper)类型,比如 int 、 Integer 等。

<constructor-arg>
    <value>111111</value>
</constructor-arg>
<property name="attributeName">
    <value>222222</value>
</property>
<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 则只能指定位于当前容器的父容器中定义的对象引用
  • bean 则基本上通吃,所以,通常情况下,直接使用bean来指定对象引用就可以了

ref下面没有其他子元素

(3)<idref>

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

使用 idref ,容器在解析配置的时候就可以帮你检查这个 beanName 到底是否存在,而不用等到运行时才发现这个 beanName 对应的对象实例不存在。

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

(4)内部<bean>

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

<bean id="djNewsProvider" class="..FXNewsProvider">
<constructor-arg index="0">
    <bean class="..impl.DowJonesNewsListener">
    </bean>
</constructor-arg>
<constructor-arg index="1">
    <ref bean="djNewsPersister"/>
</constructor-arg>
</bean>

(5)<list>

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

(6)<set>

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

(7)<map>

与列表(list)使用数字下标来标识元素不同,映射(map)可以通过指定的键(key)来获取相应的值。

对于 <map> 来说,它可以内嵌任意多个 <entry> ,每一个 <entry> 都需要为其指定一个键和一个值,就跟真正的 java.util.Map 所要求的一样。

  • 指定 entry 的键。可以使用 <entry> 的属性—— key 或者 key-ref 来指定键,也可以使用 <entry>的内嵌元素 <key> 来指定键。
  • 指定 entry 对应的值。 <entry> 内部可以使用的元素,除了 <key> 是用来指定键的,其他元素可以任意使用,来指定 entry 对应的值。

(8)<props>

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

每个 <props> 可以嵌套多个 <prop> ,每个 <prop> 通过其 key 属性来指定键,在 <prop> 内部直接指定其所对应的值。 <prop> 内部没有任何元素可以使用,只能指定字符串,这个是由 java.util.Properties 的语意决定的。

(9)<null/>

4、depends-on

在bean定义中使用 depends-on 来要求容器在初始化自身实例之前首先实例化依赖对象。

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

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

 5、autowire

除了可以通过配置明确指定bean之间的依赖关系,Spirng还提供了根据bean定义的某些特点将相互依赖的某些bean直接自动绑定的功能。通过 <bean> 的 autowire 属性,可以指定当前bean定义采用某种类型的自动绑定模式。

Spring提供了5种自动绑定模式,即 no 、 byName 、 byType 、 constructor 和 autodetect 。

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

<bean id="beanName" class="..."/>
或者
<bean id="beanName" class="..." autowire="no"/>

2、byName。按照类中声明的实例变量的名称,与XML配置文件中声明的bean定义的 beanName 的值进行匹配,相匹配的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>

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

<bean id="fooBean" class="...Foo" autowire="byType">
</bean>

<bean id="anyName" class="...Bar">
</bean>

4、constructor。 constructor 类型则是针对构造方法参数的类型而进行的自动绑定,它同样是 byType 类型的绑定模式。不过, constructor 是匹配构造方法的参数类型,而不是实例属性的类型。 

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>

5、autodetect

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

6、 dependency-check

  • none。不做依赖检查。将 dependency-check 指定为 none 跟不指定这个属性等效。默认情况下,容器以此为默认值。
  • simple。果将 dependency-check 的值指定为 simple ,那么容器会对简单属性类型以及相关的collection进行依赖检查,对象引用类型的依赖除外。
  • object。只对对象引用类型依赖进行检查。
  • all。将 simple 和 object 相结合,也就是说会对简单属性类型以及相应的collection和所有对象引用类型的依赖进行检查。

7、 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 。

仅指定 lazy-init-bean 的 lazy-init 为 true ,并不意味着容器就一定会延迟初始化该bean的实例。如果某个非延迟初始化的bean定义依赖于 lazy-init-bean ,那么毫无疑问,按照依赖决计的顺序,容器还是会首先实例化 lazy-init-bean ,然后再实例化后者。

1.2.3、继承

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

使用继承关系配置的 FXNewsProvider 和 SpecificFXNewsProvider 

<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定义模板化的目的。

使用模板化配置形式配置 FXNewsProvider 和 SpecificFXNewsProvider

<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定义不需要实例化。 该bean定义只是一个配置模板,不对应任何对象。 superNews-Provider 和 subNewsProvider 通过 parent 指向这个模板定义,就拥有了该模板定义的所有属性配置。

默认情况下, ApplicationContext 会在容器启动的时候就对其管理的所有bean进行实例化,只有标志为 abstract 的bean除外。

 1.2.4、bean的scope

BeanFactory 除了拥有作为IoC Service Provider的职责,作为一个轻量级容器,它还有着其他一些职责,其中就包括对象的生命周期管理。

scope用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象。

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

我们可以通过使用 <bean> 的singleton或者scope属性来指定相应对象的scope,其中,scope属性只能在XSD格式的文档声明中使用。

DTD:
<bean id="mockObject1" class="...MockBusinessObject" singleton="false"/>
XSD:
<bean id="mockObject2" class="...MockBusinessObject" scope="prototype"/>

1、singleton

容器根据bean定义的scope语意来决定bean对象实例的存活时间。

singleton类型的bean所具有的特性:

  1. 对象实例数量。singleton类型的bean定义,在一个容器中只存在一个共享实例,所有对该类型bean的依赖都引用这一单一实例。
  2. 对象存活时间。singleton类型bean定义,从容器启动,到它第一次被请求而实例化开始,只要容器不销毁或者退出,该类型bean的单一实例就会一直存活。
  3. 通常情况下,如果你不指定bean的scope,singleton便是容器默认的scope。

2、prototype

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

3、request、session和global session

这三个scope类型是Spirng 2.0之后新增加的,它们不像之前的singleton和prototype那么“通用”,因为它们只适用于Web应用程序,通常是与 XmlWebApplicationContext 共同使用。

1、request

<bean id="requestProcessor" class="...RequestProcessor" scope="request"/>

Spring容器,即 XmlWebApplicationContext 会为每个HTTP请求创建一个全新的 RequestProcessor 对象供当前请求使用,当请求结束后,该对象实例的生命周期即告结束。当同时有10个HTTP请求进来的时候,容器会分别针对这10个请求返回10个全新的 RequestProcessor 对象实例,且它们之间互不干扰。 

2、session

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

Spring容器会为每个独立的session创建属于它们自己的全新的 UserPreferences 对象实例。 

3、global session

<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>

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

4、自定义scope类型

在Spring 2.0之后的版本中,容器提供了对scope的扩展点。

public interface Scope {
    Object get(String name, ObjectFactory objectFactory);
    Object remove(String name);
    void registerDestructionCallback(String name, Runnable callback);
    String getConversationId();
}

 要实现自己的scope类型,首先需要给出一个 Scope 接口的实现类,接口定义中的4个方法并非都是必须的,但 get 和 remove 方法必须实现。

1.2.5、工厂方法与 FactoryBean

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

<bean id="foo" class="...Foo">
    <property name="barInterface">
        <ref bean="bar"/>
    </property>
</bean>
<bean id="bar" class="...StaticBarInterfaceFactory" factory-method="getInstance"/>

class 指定静态方法工厂类, factory-method 指定工厂方法名称,然后,容器调用该静态方法工厂类的指定工厂方法( getInstance ),并返回方法调用后的结果,即 BarInterfaceImpl 的实例。 

<bean id="foo" class="...Foo">
    <property name="barInterface">
        <ref bean="bar"/>
    </property>
</bean>
<bean id="bar" class="...StaticBarInterfaceFactory" factory-method="getInstance">
    <constructor-arg>
        <ref bean="foobar"/>
    </constructor-arg>
</bean>
<bean id="foobar" class="...FooBar"/>

 可以通过 <constructor-arg> 来指定工厂方法需要的参数。

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

<bean id="foo" class="...Foo">
    <property name="barInterface">
        <ref bean="bar"/>
    </property>
</bean>
<bean id="barFactory" class="...NonStaticBarInterfaceFactory"/>

<bean id="bar" factory-bean="barFactory" factory-method="getInstance"/>

3、FactoryBean 

FactoryBean 是Spring容器提供的一种可以扩展容器对象实例化逻辑的接口。

public interface FactoryBean {
    Object getObject() throws Exception;
    Class getObjectType();
    boolean isSingleton();
}

getObject() 方法会返回该 FactoryBean “生产”的对象实例,我们需要实现该方法以给出自己的对象实例化逻辑; getObjectType() 方法仅返回 getObject() 方法所返回的对象的类型,如果预先无法确定,则返回 null ; isSingleton() 方法返回结果用于表明,工厂方法( getObject() )所“生产”的对象是否要以singleton形式存在于容器中。如果以singleton形式存在,则返回 true ,否则返回 false 。

Object nextDayDate = container.getBean("nextDayDate");
assertTrue(nextDayDate instanceof DateTime);
Object factoryBean = container.getBean("&nextDayDate");

assertTrue(factoryBean instanceof FactoryBean);
assertTrue(factoryBean instanceof NextDayDateFactoryBean);
Object factoryValue = ((FactoryBean)factoryBean).getObject();

assertTrue(factoryValue instanceof DateTime);
assertNotSame(nextDayDate, factoryValue);
assertEquals(((DateTime)nextDayDate).getDayOfYear(),((DateTime)factoryValue).getDayOfYear());

1.2.6、方法注入

public class MockNewsPersister implements IFXNewsPersister {
    private FXNewsBean newsBean;
    public void persistNews(FXNewsBean bean) {
        persistNewes();
    }
    public void persistNews()
    {
        System.out.println("persist bean:"+getNewsBean());
    }
    public FXNewsBean getNewsBean() {
        return newsBean;
    }
    public void setNewsBean(FXNewsBean newsBean) {
        this.newsBean = newsBean;
    }
}

保证 getNewsBean() 方法每次从容器中取得新的 FXNewsBean 实例的方法:

1、方法注入 

方法声明需要符合的规格定义如下:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);
<bean id="newsBean" class="..domain.FXNewsBean" singleton="false">
</bean>
<bean id="mockPersister" class="..impl.MockNewsPersister">
    <lookup-method name="getNewsBean" bean="newsBean"/>
</bean>

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

2、使用 BeanFactoryAware 接口

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

public class MockNewsPersister implements IFXNewsPersister,BeanFactoryAware {
    private BeanFactory beanFactory;
    public void setBeanFactory(BeanFactory bf) throws BeansException {
    this.beanFactory = bf;
    }

    public void persistNews(FXNewsBean bean) {
        persistNews();
    }

    public void persistNews()  {
        System.out.println("persist bean:"+getNewsBean());
    }

    public FXNewsBean getNewsBean() {
        return beanFactory.getBean("newsBean");
    }
}

 3、使用 ObjectFactoryCreatingFactoryBean

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

public class MockNewsPersister implements IFXNewsPersister {
    private ObjectFactory newsBeanFactory;
    public void persistNews(FXNewsBean bean) {
        persistNews();
    }
    public void persistNews()
    {
        System.out.println("persist bean:"+getNewsBean());
    }
    public FXNewsBean getNewsBean() {
        return newsBeanFactory.getObject();
    }
    public void setNewsBeanFactory(ObjectFactory newsBeanFactory) {
        this.newsBeanFactory = newsBeanFactory;
    }
}
<bean id="newsBean" class="..domain.FXNewsBean" singleton="false">
</bean>

<bean id="newsBeanFactory" class="org.springframework.beans.factory.config.
ObjectFactoryCreatingFactoryBean">
    <property name="targetBeanName">
        <idref bean="newsBean"/>
    </property>
</bean>

<bean id="mockPersister" class="..impl.MockNewsPersister">
    <property name="newsBeanFactory">
        <ref bean="newsBeanFactory"/>
    </property>
</bean>

1.2.7、方法替换

 方法替换更多体现在方法的实现层面上,它可以灵活替换或者说以新的方法实现覆盖掉原来某个方法的实现逻辑。

首先,我们需要给出 org.springframework.beans.factory.support.MethodReplacer 的实现类,在这个类中实现将要替换的方法逻辑。

有了要替换的逻辑之后,我们就可以把这个逻辑通过 <replaced-method> 配置到 FXNewsProv-ider 的bean定义中,使其生效。

1.3、容器探索

1.3.1、容器启动过程

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

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

1、容器启动阶段

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

2、Bean实例化阶段

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

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

1.3.2、容器扩展

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

如果要自定义实现 BeanFactoryPostProcessor ,通常我们需要实现 org.springframework.beans.factory.config.BeanFactoryPostProcessor 接口。同时,因为一个容器可能拥有多个 BeanFactoryPostProcessor ,这个时候可能需要实现类同时实现Spring的 org.springframework.core.Ordered 接口,以保证各个 BeanFactoryPostProcessor 可以按照预先设定的顺序执行(如果顺序紧要的话)。

1、IoC容器BeanFactory

2、ApplicationContext

1、PropertyPlaceholderConfigurer 

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

2、PropertyOverrideConfigurer

可以通过 PropertyOverrideConfigurer 对容器中配置的任何你想处理的bean定义的property信息进行覆盖替换。

如果要对容器中的某些bean定义的property信息进行覆盖,我们需要按照如下规则提供一个PropertyOverrideConfigurer 使用的配置文件:beanName.propertyName=value。也就是说,properties文件中的键是以XML中配置的bean定义的 beanName 为标志开始的(通常就是 id 指定的值),后面跟着相应被覆盖的property的名称

3、CustomEditorConfigurer

它只是辅助性地将后期会用到的信息注册到容器,对 BeanDefinition 没有做任何变动。

1.3.3、了解bean的一生

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

隐式调用有如下两种情况:

  • 对于 BeanFactory 来说,对象实例化默认采用延迟初始化。通常情况下,当对象A被请求而需要第一次实例化的时候,如果它所依赖的对象B之前同样没有被实例化,那么容器会先实例化对象A所依赖的对象。这时容器内部就会首先实例化对象B,以及对象 A依赖的其他还没有被实例化的对象。这种情况是容器内部调用 getBean() ,对于本次请求的请求方是隐式的。
  • ApplicationContext 启动之后会实例化所有的bean定义,但 ApplicationContext 在实现的过程中依然遵循Spring容器实现流程的两个阶段,只不过它会在启动阶段的活动完成之后,紧接着调用注册到该容器的所有bean定义的实例化方法getBean() 。

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 定义继承了 org.springframework.beans.PropertyAccessor 接口,可以以统一的方式对对象属性进行访问; BeanWrapper 定义同时又直接或者间接继承了 PropertyEditorRegistry和 TypeConverter 接口。

在第一步构造完成对象之后,Spring会根据对象实例构造一个 BeanWrapperImpl 实例,然后将之前 CustomEditor-Configurer 注册的 PropertyEditor 复制一份给 BeanWrapperImpl 实例(这就是BeanWrapper 同时又是 PropertyEditorRegistry 的原因)。

2、各色的 Aware 接口

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

这些 Aware 接口为如下几个:

  •  org.springframework.beans.factory.BeanNameAware 。如果Spring容器检测到当前对象实例实现了该接口,会将该对象实例的bean定义对应的 beanName 设置到当前对象实例。
  • org.springframework.beans.factory.BeanClassLoaderAware 。如果容器检测到当前对象实例实现了该接口,会将 对应加载当前 bean的Classloader注入当前对象实例。默认会使用加org.springframework.util.ClassUtils类的Classloader。
  • org.springframework.beans.factory.BeanFactoryAware 。如果对象声明实现了BeanFactoryAware 接口, BeanFactory 容器会将自身设置到当前对象实例。这样,当前对象实例就拥有了一个BeanFactory 容器的引用,并且可以对这个容器内允许访问的对象按照需要进行访问。

对于 ApplicationContext 类型容器,容器在这一步还会检查以下几个 Aware 接口并根据接口定义设置相关依赖。

  • org.springframework.context.ResourceLoaderAware 。 ApplicationContext 实现了Spring的 ResourceLoader 接口。当容器检测到当前对象实例实现了ResourceLoaderAware 接口之后,会将当前 ApplicationContext 自身设置到对象实例,这样当前对象实例就拥有了其所在 ApplicationContext 容器的一个引用。
  • org.springframework.context.ApplicationEventPublisherAware。ApplicationContext作为一个容器,同时还实现了 ApplicationEventPublisher 接口,这样,它就可以作为 ApplicationEventPublisher 来使用。所以,当前 ApplicationContext 容器如果检测到当前实例化的对象实例声明了 ApplicationEventPublisherAware 接口,则会将自身注入当前对象。
  • org.springframework.context.MessageSourceAware 。 ApplicationContext 通过 MessageSource 接口提供国际化的信息支持,即I18n(Internationalization)。它自身就实现了 MessageSource 接口,所以当检测到当前对象实例实现了 MessageSourceAware 接口,则会将自身注入当前对象实例。
  • org.springframework.context.ApplicationContextAware。 如果 ApplicationContext容器检测到当前对象实现了 ApplicationContextAware 接口,则会将自身注入当前对象实例。

3、BeanPostProcessor

 BeanPostProcessor 会处理容器内所有符合条件的实例化后的对象实例。该接口声明了两个方法,分别在两个不同的时机执行,见如下代码定义:

public interface BeanPostProcessor
{
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

通常比较常见的使用 BeanPostProcessor 的场景,是处理标记接口实现类,或者为当前对象提供代理实现。 除了检查标记接口以便应用自定义逻辑,还可以通过 BeanPostProcessor 对当前对象实例做更多的处理。比如替换当前对象实例或者字节码增强当前对象实例等。

自定义 BeanPostProcessor

假设系统中所有的 IFXNewsListener 实现类需要从某个位置取得相应的服务器连接密码,而且系候,首先需要对系统中取得的密码进行解密,然后才能发送。我们将采用 BeanPostProcessor 技术,对所有的 IFXNewsListener 的实现类进行统一的解密操作。统中保存的密码是加密的,那么在 IFXNewsListener 发送这个密码给新闻服务器进行连接验证的时

1、标注需要进行解密的实现类

为了能够识别那些需要对服务器连接密码进行解密的 IFXNewsListener 实现,我们声明了接口PasswordDecodable ,并要求相关 IFXNewsListener 实现类实现该接口。

public interface PasswordDecodable {
    String getEncodedPassword();
    void setDecodedPassword(String password);
}
public class DowJonesNewsListener implements IFXNewsListener,PasswordDecodable {
    private String password;
    public String[] getAvailableNewsIds() {
        // 省略
    }
    public FXNewsBean getNewsByPK(String newsId) {
        // 省略
    }
    public void postProcessIfNecessary(String newsId) {
        // 省略
    }
    public String getEncodedPassword() {
        return this.password;
    }
    public void setDecodedPassword(String password) {
        this.password = password;
    }
}

2、实现相应的 BeanPostProcessor 对符合条件的Bean实例进行处理 

我们通过 PasswordDecodable 接口声明来区分将要处理的对象实例,当检查到当前对象实例实现了该接口之后,就会从当前对象实例取得加密后的密码,并对其解密。然后将解密后的密码设置回当前对象实例。

public class PasswordDecodePostProcessor implements BeanPostProcessor {
    public Object postProcessAfterInitialization(Object object, String beanName)
throws BeansException {
    return object;
}
    public Object postProcessBeforeInitialization(Object object, String beanName)
throws BeansException {
        if(object instanceof PasswordDecodable){
            String encodedPassword = ((PasswordDecodable)object).getEncodedPassword();
            String decodedPassword = decodePassword(encodedPassword);
            ((PasswordDecodable)object).setDecodedPassword(decodedPassword);
        }
        return object;
    }
    private String decodePassword(String encodedPassword) {
        // 实现解码逻辑
        return encodedPassword;
    }
}

 3、将自定义的 BeanPostProcessor 注册到容器

对于 BeanFactory 类型的容器来说,我们需要通过手工编码的方式将相应的 BeanPostProcessor注册到容器,也就是调用 ConfigurableBeanFactory 的 addBeanPostProcessor() 方法,见如下代码: 

ConfigurableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource(...));
beanFactory.addBeanPostProcessor(new PasswordDecodePostProcessor());
  ...

//getBean();

 对于 ApplicationContext 容器来说,事情则方便得多,直接将相应的 BeanPostProcessor 实现类通过通常的XML配置文件配置一下即可。 ApplicationContext 容器会自动识别并加载注册到容器的 BeanPostProcessor:

<beans>
<bean id="passwordDecodePostProcessor" class="package.name.PasswordDecodePostProcessor">
<!--如果需要,注入必要的依赖-->
</bean>
...
</beans>

在所有的步骤之前,也就是实例化bean对象步骤之前,容器会首先检查容器中是否注册有 InstantiationAwareBeanPost-Processor 类型的 BeanPostProcessor 。如果有,首先使用相应的InstantiationAwareBeanPostProcessor 来构造对象实例。 

4、  InitializingBean 和 init-method

org.springframework.beans.factory.InitializingBean 是容器内部广泛使用的一个对象命周期标识接口,其定义如下:

public interface InitializingBean { 
    void afterPropertiesSet() throws Exception;
}

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

通过 init-method ,系统中业务对象的自定义初始化操作可以以任何方式命名,而不再受制于InitializingBean 的 afterPropertiesSet() 。如果系统开发过程中规定:所有业务对象的自定义初始化操作都必须以 init() 命名,为了省去挨个 <bean> 的设置 init-method 这样的烦琐,我们还可以通过最顶层的 <beans> 的 default-init-method 统一指定这一 init() 方法名。

<beans>
    <bean id="tradeDateCalculator" class="FXTradeDateCalculator" ➥
    init-method="setupHolidays">
        <constructor-arg>
            <ref bean="sqlMapClientTemplate"/>
        </constructor-arg>
    </bean>
    <bean id="sqlMapClientTemplate" ➥
        class="org.springframework.orm.ibatis.SqlMapClientTemplate">
        ...
    </bean>
    ...
</beans>

 5、 DisposableBean 与 destroy-method

 DisposableBean和destroy-method 为对象提供了执行自定义销毁逻辑的机会。

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

对于 BeanFactory 容器来说。我们需要在独立应用程序的主程序退出之前,或者其他被认为是合适的情况下(依照应用场景而定),调用 ConfigurableBeanFactory 提供destroySingletons() 方法销毁容器中管理的所有singleton类型的对象实例。

如果不能在合适的时机调用 destroySingletons() ,那么所有实现了 DisposableBean 接口的对象实例或者声明了 destroy-method 的bean定义对应的对象实例,它们的自定义对象销毁逻辑就形同虚设,因为根本就不会被执行!

对于 ApplicationContext 容器来说。道理是一样的。但 AbstractApplicationContext 为我们提供了 registerShutdownHook() 方法,该方法底层使用标准的 Runtime 类的 addShutdownHook() 方式来调用相应bean对象的销毁逻辑,从而保证在Java虚拟机退出之前,这些singtleton类型的bean对象实例的自定义销毁逻辑会被执行。当然 AbstractApplicationContext 注册的 shutdownHook 不只是调用对象实例的自定义销毁逻辑,也包括 ApplicationContext 相关的事件发布等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值