Spring 揭秘之Spring的IoC容器之BeanFactory(2)BeanFactory的XML之旅

第四章Spring的IoC容器之BeanFactory(2)

BeanFactory的XML之旅

beans和bean

XML配置格式是Spring支持最完整,功能最强大的表达方式;一方面这得益于XML良好的语意表达能力;另一方面, 就是Spring框架从开始就自始至终保持XML配置加载的统一性;不管是ApplicationContext还是BeanFactory,都遵循一致的XML格式:

<?xml version="1.0" encoding="UTF-8"?>
<beans 
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           https://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="..." class="...">   
        <!-- collaborators and configuration for this bean go here -->
    </bean>
    
    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>
    
    <!-- more bean definitions go here -->
</beans>

所有注册到容器的业务对象,在Spring中称为Bean。<beans>是XML配置文件中最顶层的元素,它下面可以包0或者1个<description>和多个<bean>以及<import>或者<alias> 。它拥有相应的属性( attribute)对所辖的<bean>进行统一的默认行为设置:

  1. default-lazy-init:默认为false;用来标记是否对其所有的Bean进行延迟初始化;
  2. default-autowire:取值可以为no、byName、byType、constructor以及autodetect;默认为no;用来标记是否采用自动绑定以及使用哪种方式进行绑定;
  3. default-dependency-check:取值可以为none、object、simple、all;默认为none;用来标记是否对对象进行依赖检查以及检查类型;
  4. default-destroy-method:用来标志其所包含的bean的销毁方法;
  5. default-init-method:用来标记其所包含的bean的初始化方法;

这些是beans作为bean的顶级元素所具有的配置属性,使用它们可以简化具体bean的配置,不使用也没有关系;

description、import和alias

这些元素都是非必须的;description元素主要提供一些描述信息;import用于在该xml文件中引入其他xml配置文件;alias用于为bean指定别名,指定完别名后,通过name和别名都可以引用该bean;适合在bean的name比较长的时候使用;

bean

  1. id属性:通过id属性来指定当前注册对象的beanName是什么。实际上,并非任何情况下都需要指定每个<bean>的id,有些情况下, id可以省略,比如后面会提到的内部<bean>以及不需要根据beanName明确依赖关系的场合等。**除了可以使用id来指定<bean>在容器中的标志,还可以使用name属性来指定<bean>的别名。**与id相比,name属性更加灵活:可以通过逗号、空格或者冒号分割指定多个name。name属性和通过alias为bean指定别名的效果是一样的;
  2. class属性:指定bean的类别;大多数情况下,该属性是必须的;但是在使用抽象配置模板的时候,该属性就不是必须的了。

通过xml实现构造方法注入

直接看例子:

	<bean id="person" class="Person">
		<constructor-arg ref="dog"/>
	</bean>
	<bean id="person2" class="Person">
        <constructor-arg value="666" type="int"/>
	</bean>
	<bean id="dog" class="Dog"/>

我们在创建Person时需要传入Dog实例,此时我们便可以通过constructor-arg元素来指定bean或者value。

关于constructor-arg元素,我们需要注意以下两种情况:如果Person的构造函数是两条Dog,那么如何标记谁是第一个dog,谁是第二个dog呢?如果Person有两个构造函数,一个接受dog,一个接受int型的age,又该如何区分这两个构造函数呢?

解决方法就是constructor-arg元素的type和index属性。type表示该参数的类型,index表示该参数是构造函数所需的第几个参数,从0开始;

通过xml实现Setter方法注入

构造函数注入是通过constructor-arg实现的,setter方法注入则是通过property元素实现的

	<bean id="person" class="Person">
        <property name="myDog" ref="dog"/>
        <property name="myName" value="Nil Xuan"/>
	</bean>
	<bean id="dog" class="Dog"/>

如果只使用setter方法来注入依赖的话,记得提供默认的构造函数;当然,它们也可以和谐地一起工作,没有问题;

property和constructor-arg中可用的配置

  1. bean:就是所谓的内部bean啦。可能我们所依赖的对象只有当前一个对象引用,或者某个对象定义我们不想其他对象通过<ref>引用到它。这时,我们可以使用内嵌的<bean> ,同时,该bean就不需要id了,因为没人可以引用到它啊。**内部<bean>的配置只是在位置上有所差异,但配置项上与其他的<bean>是没有任何差别的。**也就是说, <bean>内嵌的所有元素,内部<bean>同样可以使用。如果内部<bean>对应的对象还依赖于其他对象,你完全可以像其他独立的<bean>定义一样为其配置相关依赖,没有任何差别。

  2. ref:用来引用容器中其他的对象实例,可以通过local、parent、bean等来引用bean;local只能引用与当前配置的对象处与同一配置文件的bean;parent则只能引用父容器中的对象;BeanFactory是分层次的哦;bean则通吃啦;

  3. idref:如果要为当前对象注入所依赖的对象的名称,而不是引用,那么通常情况下,可以使用<value>来达到这个目的,但这种场合下,使用idref才是最为合适的。因为使用idref,容器在解析配置的时候就可以帮你检查这个beanName到底是否存在,而不用等到运行时才发现这个beanName对应的对象实例不存在。毕竟,输错名字的问题很常见。

  4. value:为主体对象注入简单的数据类型,包括Integer等包装类;容器会做适当的转换工作;

  5. null:表示空元素,即null。对于String来说,如果使用<value><value>,则表示"",而不是null。

  6. list:为对应对象注入类型为List及其子类或者数组类型的依赖对象;各元素有序;

  7. set:为对应对象注入类型为Set及其子类的依赖对象;元素无序;

  8. map:为对应对象注入类型为Map及其子类的依赖对象;元素以键值对的形式注入;常见格式如下:

    <map>
    	<entry key="stringKey">
        	<value>NilXuan</value>
        </entry>
        <entry>
    		<key>objectKey</key>
    		<ref bean="someObject"/>
        </entry>
        <entry key-ref="listKey">
    		<list>
            	...
    		</list>
        </entry>
    </map>
    

    entry作为map的直接子元素,其中可以使用key或者key-ref来指定一个键;通常key用于指定简单类型的键,key-ref用于指定对象的引用作为键;

    在entry内部,除了key用来指定键,其余元素可以任意使用,作为entry对应的值。

  9. props:是简化后的map,或者说是特殊的map,对应Properties类,只能以String作为键,所以其使用也就更简单了:

    <property name="valueSet">
    	<props>
        	<prop key="author">NilXuan</prop>
            <prop key="interest">Code</prop>
        </props>
    </property>
    

    每个props可以拥有多个prop,每个prop使用key属性指定键,内部只能使用字符串;

depends-on

通常情况下,可以直接通过之前提到的所有元素,来显式地指定bean之间的依赖关系。容器在初始化当前bean定义的时候,会根据这些元素所标记的依赖关系,首先实例化当前bean定义所依赖的其他bean定义

但是没有通过类似<ref>的元素明确指定对象A依赖于对象B的话,如何让容器在实例化对象A之前首先实例化对象B呢?这就是depends-on可能的使用场景。

大部分情况下,是那些拥有静态代码块初始化代码或者数据库驱动注册之类的场景。如果说ClassA拥有多个类似的非显式依赖关系,那么,你可以在ClassA的depends-on中通过逗号分割各个beanName。

<bean id="classA" class="ClassA" depends-on="classB,classC,classD"/>

如此,在实例化A的时候,容器会先实例化B、C、D;

autowire

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

  1. no:容器的默认自动绑定模式,也就是不采取任何形式的自动绑定,通过手动配置文件来描述对象间的依赖关系;
  2. byName:根据类中实例变量的名称,与xml配置文件中声明的bean的beanName(也就是id)进行匹配,匹配的bean定义将自动绑定到该实例变量上。
  3. byType:根据类的定义,分析其所依赖的对象类型,然后在容器中进行寻找,如果找到类型一致的对象,则将其绑定到该类的实例对象上。需要注意的是,**如果找不到,那么容器不做设置;如果找到了多个,那么。。。容器会告诉你它也不知道该如何选择,于是。。。你只好手动明确配置!**也就是说:byType只能保证,在容器中只存在一个符合条件的依赖对象的时候才会发挥作用。
  4. constructor:constructor针对构造方法参数的类型进行匹配,从而实现自动绑定,它同样是byType类型的绑定模式,只是匹配的对象是构造函数的参数;
  5. autodetect是byType和constructor模式的结合体,如果对象拥有无参的构造方法,那么容器会使用byType进行自动绑定,否则就先使用constructor模式,然后使用对未绑定的属性进行byType模式的自动绑定;

关于自动绑定,需要注意的是:

  1. 手动明确指定的绑定关系总会覆盖自动绑定模式;
  2. 自动绑定模式只适合于除“原生类型、String类型以及Class类型”及其数组对象之外的对象;

dependency-check

我们可以使用每个<bean>的dependency-check属性对其所依赖的对象进行最终检查,该功能主要与自动绑定结合使用,可以保证当自动绑定完成后,最终确认每个对象所依赖的对象是否按照所预期的那样被注入

当然,并不是说不可以与平常的明确绑定方式一起使用。该功能可以帮我们检查每个对象某种类型的所有依赖是否全部已经注入完成,不过可能无法细化到具体的类型检查。

但某些时候,使用setter方法注入就是为了拥有某种可以设置也可以不设置的灵活性,所以,这种依赖检查并非十分有用,尤其是在手动明确绑定依赖关系的情况下可以通过dependency-check指定容器帮我们检查某种类型的依赖,基本上有如下4种类型的依赖检查:

  1. none:默认值,即不做检查;
  2. simple:容器对简单属性及其collection属性进行检查;其余不检查;
  3. object:只对对象引用类型依赖检查;
  4. all:simple和object的结合;

该功能。。。。。。基本上不使用,如果控制得当的话;

lazy-init

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

与BeanFactory不同, ApplicationContext在容器启动的时候,就会马上对所有的“ singleton的bean定义” 进行实例化操作
通常这种默认行为是好的,因为如果系统有问题的话,可以在第一时间发现这些问题,但有时,我们不想某些bean定义在容器启动后就直接实例化,可能出于容器启动时间的考虑,也可能出于其他原因的考虑。

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

当然,仅设定lazy-init为true并不意味着对该bean的初始化一定是延迟的,因为有可能其被非延迟初始化的bean所依赖;

继承

xml里通过继承来实现bean的纵向关联;纵向关联是通过bean的parent实现的。parent属性还可以与abstract属性结合使用,达到将相应bean定义模板化的目的。

bean定义通过abstract属性声明为true,说明这个bean定义不需要实
例化,所以也就不要求指定class属性了。容器对标记为abstract的bean并不进行实例化;所以如果你不想容器在初始化的时候实例化某些对象,那么可以将其abstract属性赋值true,以避免容器将其实例化。对于ApplicationContext容器尤其如此,因为默认情况下, ApplicationContext会在容器启动的时候就对其管理的所有bean进行实例化,只有标志为abstract的bean除外。

bean的scope

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

不过,我更希望告诉你scope这个词到底代表什么意思,至于你怎么称呼它反而不重要。scope用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁该对象;

配置中的bean定义可以看作是一个模板,容器会根据这个模板来构造对象。但是要根据这个模板构造多少对象实例,又该让这些构造完的对象实例存活多久,则由容器根据bean定义的scope语意来决定

scope的取值有:singleton和prototype以及在web环境下使用的session、request和global session。

  1. singleton:Spring的IoC容器中,只有一个对象实例;所有对该对象的引用共享该实例;它几乎和IoC容器有着同样的寿命;需要注意的一点是,不要因为名字的原因而与GoF所提出的Singleton模式相混淆,二者的语意是不同的: 标记为singleton的bean是由容器来保证这种类型的bean在同一个容器中只存在一个共享实例;而Singleton模式则是保证在同一个Classloader中只存在一个这种类型的实例;
  2. prototype:针对声明为拥有prototype scope的bean定义,容器在接到该类型对象的请求的时候,会每次都重新
    生成一个新的对象实例给请求方。虽然这种类型的对象的实例化以及属性设置等工作都是由容器负责的,但是只要准备完毕,并且对象实例返回给请求方之后,容器就不再拥有当前返回对象的引用,请求方需要自己负责当前返回对象的后继生命周期的管理工作,包括该对象的销毁也就是说,容器每次返回给请求方一个新的对象实例之后,就任由这个对象实例“自生自灭”了。
  3. request:Spring容器为每个HTTP请求创建一个全新的对象供当前请求使用,当请求结束后,对象生命周期宣布结束;类似prototype,只是使用场景略有不同;
  4. session:Spring容器为每个独立的session创建属于它们自己的标记对象;
  5. global session:只有在基于portlet的web应用程序中才有意义。

工厂方法与FactoryBean

在强调“面向接口编程”的同时,有一点需要注意:虽然对象可以通过声明接口来避免对特定接口实现类的过度耦合,但总归需要一种方式将声明依赖接口的对象与接口实现类关联起来

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

针对使用工厂方法模式实例化对象的方式,Spring容器也提供了对应的集成;

静态工厂方法
<bean id="foo" class="...Foo">
	<property name="barInterface">
		<ref bean="bar"/>
	</property>
</bean>
<bean id="bar" class="...StaticBarInterfaceFactory" factory-method="getInstance"/>

我们为foo对象注入bar,但是在beanName为bar的bean定义中,我们指定的不是具体的bar实现类,而是一个静态工厂类,同时通过factory-method属性指定了静态方法;

此时,对bar的引用,实际上是对调用静态类的静态方法的返回结果的引用;

当然,如果该静态方法需要参数,我们也可以通过constructor-arg为其传入;

非静态工厂方法

因为非静态工厂方法需要通过实例对象去调用,所以我们需要通过bean的factory-bean属性来指定factory对象,然后通过factory-method指定具体的方法即可,同样滴,我们也可通过constructor-arg来传入相关参数。

实际上,不论怎样,容器总要通过调用一个方法来创建一个对象,所以我们需要做的就是指定方法,然后传递参数即可;没错,传递参数就是通过constructor-arg来完成的;

FactoryBean

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

当某些对象的实例化过程过于烦琐,通过XML配置过于复杂,使我们宁愿使用Java代码来完成这个实例化过程的时候,或者,某些第三方库不能直接注册到Spring容器的时候, 就可以实现FactoryBean接口,给出自己的对象实例化逻辑代码

需要注意的是,对FactoryBean类型的bean定义,通过正常的id引用,容器返回的是FactoryBean所“生产”的对象类型,而非FactoryBean实现本身。如果一定要取得FactoryBean本身的话,可以通过在bean定义的id之前加前缀&来达到目的。

偷梁换柱

有如下场景:A类对象依赖B类对象,A对外提供一个获取B类对象的方法M,但是要求该方法每次返回的B都是一个全新的B,换言之,是从容器中获得,而不是A缓存的;

方法注入

一种方法是,我们通过对M做一些配置来实现该效果;当然,对M本身也是有要求的,它的签名需要符合以下结构:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

也就是说,该方法必须能够被子类实现或者覆写,因为容器会为我们要进行方法注入的对象使用Cglib动态生成一个子类实现,从而替代当前对象;

在xml里需要为A配置一个lookup-method元素,name指定M的方法名,bean属性指定要返回的bean的引用;

以上方法就是所谓的方法注入啦;当然,还有其他方法,比如:

殊途同归
  1. 使用BeanFactoryAware接口获得容器的引用,然后在对应方法里直接通过容器获取对象就好啦;
  2. 使用ObjectFactory;我们通过ObjectFactoryCreatingFactoryBean来获得ObjectFactory,然后在xml配置目标对象,也就是B,之后直接调用ObjectFactory的getObject方法即可;实际上,ObjectFactory实例只是特定于与Spring容器进行交互的一个实现,好处就是隔离了客户端对BeanFactory的直接引用;
方法替换

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

要使用方法替换这一方法,我们需要一个MethodReplacer的实现类,然后通过A的replace-method元素做相应配置;

<bean id="A">
	<replaced-method name="getB" replacer="providerReplacer">
	</replaced-method>
</bean>
<bean id="providerReplacer" class="MyMethodReplacer"/>

到这里,Spring的XML之旅就算是暂告一段落啦~~(终于结束了)~~。到最后,方法替换实际上就有一点点“AOP”的味道啦,当然,我们得先走近ApplicationContext,掌握了Spring IoC之后再开始AOP之旅~

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值