【Spring系列】- 如何表达Bean之间依赖性

【Spring系列】- 如何表达Bean之间依赖性


一、<beans><bean>

XML格式的容器信息管理方式是Spring提供的最为强大、支持最为全面的方式。所有使用XML文件进行配置信息加载的Spring IoC容器 (包括BeanFactory和ApplicationContext的XML相应实现) 都使用统一的XML格式

  • 在Spring 2.0版本之前,XML格式由Spring提供的DTD规定,即所有Spring容器加载的XML配置文件的头部都需要DOCTYPE声明
  • 从Spring 2.0版本之后,Spring在继续保持向前兼容的前提下,既可以继续使用DTD方式的DOCTYPE进行配置文件格式的限定,又引入了基于XML Schema的文档声明(基于XSD的文档声明)

1. XML总体格式

每一个对象在XML中的映射也对应一个叫做的元素。在XML中把这些叫做的元素组织起来的,就叫做,多个组成一个,是XML配置文件中最顶层的元素,它下面可以包含0或者1个和多个以及或者

2. <beans>的属性attribute

用于对所辖的进行统一的默认行为设置,包括:

  • 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有按照某种规则使用了相同名称的对象销毁方法,可以通过这个属性统一指定

3. <description><import><alias>

  • 可以通过在配置的文件中指定一些描述性的信息。通常省略
  • 通常情况下,可以根据模块功能或者层次关系,将配置信息分门别类地放到多个配置文件中。可以在主要的配置文件中通过元素对其所依赖的配置文件进行引用,类似于。但是,这个功能在我看来价值不大,因为容器实际上可以同时加载多个配置,没有必要非通过一个配置文件来加载所有配置
  • 可以通过为某些起别名,通常情况下是为了减少输入。比如,假设有个,它的名称为dataSourceForMasterDatabase,可以为其添加一个,像这样

4. Bean的配置形式

  • 每个业务对象作为个体,在Spring的XML配置文件中是与元素一 一对应的

  • 最基础的对象配置形式:

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

    用于标识区分每个注册到容器的对象。通过id属性来指定当前注册对象的beanName是什么,这里,通过id指定beanName为djNewsListener。有些情况下,id可以省略,除了可以使用id来指定在容器中的标志,还可以使用name属性来指定的别名(alias)。与id属性相比,name属性的灵活之处在于,name可以使用id不能使用的一些字符,如/,还可以通过逗号、空格或冒号分割指定多个name。name的作用跟使用为id指定多个别名基本相同

  • class属性

    每个注册到容器的对象都需要通过元素的class属性指定其类型。在大部分情况下,该属性是必须的,仅在少数情况下不需要指定,如在使用抽象配置模板的情况下

二、如何表达依赖性

各个业务对象之间会相互协作来更好地完成同一使命,这时,各个业务对象之间的相互依赖就是无法避免的。

在Spring的IoC容器的XML配置中,如何表达依赖性:

1. XML中通过构造方法注入依赖

  • 按照Spring的IoC容器配置格式,要通过构造方法注入方式,为当前业务对象注入其所依赖的对象,需要使用,并通过元素来指明容器将为djNewsProvider这个注入通过所引用的Bean实例

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

    • type属性: 指定采用哪个参数类型的构造方法

      <bean id="mockBO" class="..MockBusinessObject"> 
      	<constructor-arg type="int"> 
      		<value>111111</value> 
      	</constructor-arg>
      </bean>
      
    • index属性: 当某个业务对象的构造方法同时传入了多个类型相同的参数时,如果配置项信息和对象参数可以按照顺序初步对应的话,Spring还是可以正常工作的

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

      但如果XML中参数没按顺序给出的话,就得加上index指定是第几个参数了

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

2. XML中通过 setter方法注入依赖

  • 与构造方法注入可以使用注入配置相对应,Spring为setter方法注入提供了元素。有一个name属性(attribute),用来指定该注入的对象的实例变量名称,再通过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>
    

    使用的setter方法注入和使用的构造方法注入并不是水火不容的。实际上,如果需要,可以同时使用这两个元素。比如,业务对象依赖两个对象,但只提供了一个只有一个dependency1参数的构造方法,又为dependency2提供了相应的setter方法,则可以:

    <bean id="mockBO" class="..MockBusinessObject"> 
    	<constructor-arg value="11111"/> 
    	<property name="dependency2" value="22222"/> 
    </bean>
    

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

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

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

    <constructor-arg> 
    	<value>111111</value> 
    </constructor-arg> 
    <property name="attributeName"> 
    	<value>222222</value> 
    </property>
    
  • <ref> 使用ref来引用容器中其他的对象实例,可以通过ref的local、parent和bean属性来指定引用的对象的beanName是什么。local、parent和bean的区别在于:

    local只能指定与当前配置的对象在同一个配置文件的对象定义的名称;parent则只能指定位于当前容器的父容器中定义的对象引用;bean则基本上通吃,所以,通常直接使用bean来指定对象引用就行

    <constructor-arg>
        <ref local="djNewsPersister"/> 
    </constructor-arg> 
    # 或者
    <constructor-arg> 
    	<ref parent="djNewsPersister"/> 
    </constructor-arg> 
    # 或者
    <constructor-arg> 
    	<ref bean="djNewsPersister"/> 
    </constructor-arg>
    
  • <idref> 如果要为当前对象注入所依赖的对象的名称,而不是引用,可以使用,但使用idref才是最为合适的。因为使用idref,容器在解析配置时就可以检查这个beanName到底是否存在,而不用等到运行时才发现这个beanName对应的对象实例不存在

    <property name="newsListenerBeanName"> 
    	<idref bean="djNewsListener"/> 
    </property>
    
  • 内部<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>
    
  • <list> 对应注入对象类型为java.util.List及其子类或数组类型的依赖对象,通过可以有序地为当前对象注入以collection形式声明的依赖

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

    元素内部可以嵌套其他元素,并且可以像param1所展示的那样夹杂配置:

    <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>
    
  • <set> 如果说可以帮你有序地注入一系列依赖的话,那么就是无序的。对应注入Java Collection中类型为java.util. Set或者其子类的依赖对象

    public class MockDemoObject { 
    	private Set valueSet; 
    	// 相应的setter和getter方法 ...
    }
    

    从配置上来说,这样多层嵌套、多元素混杂配置是完全没有问题的,但很危险:

    <property name="valueSet"> 
    	<set> 
    		<value> something</value> 
    		<ref bean="someBeanName"/> 
    		<bean class="..."/> 
    		<list> 
     		... 
    		</list> 
    	</set> 
    </property>
    
  • <map> 与列表(list)使用数字下标来标识元素不同,映射(map)可以通过指定的键(key)来获取相应的值。与和的相同点在于,都是为主体对象注入Collection类型的依赖,不同点在于它对应注入java.util.Map或者其子类类型的依赖对象

    <set> 如果说可以帮你有序地注入一系列依赖的话,那么就是无序的。对应注入Java Collection中类型为java.util. Set或者其子类的依赖对象

    public class MockDemoObject { 
    	private Map mapping;
    	// 相应的setter和getter方法 ...
    }
    

    对于来说,它可以内嵌任意多个,每一个都需要为其指定一个键和一个值,就跟真正的java.util.Map所要求的一样。可以使用的属性key或key-ref,或的内嵌元素,来指定的键

    <property name="mapping">
        <map>
            <entry key="strValueKey">
                <value>something</value>
            </entry>
            <entry key="strValueKey2" value="nothing"/>
            <entry>
                <key>objectKey</key>
                <ref bean="someObject"/>
            </entry>
            <entry key-ref="lstKey">
                <list>
                    ...
                </list>
            </entry>
        </map> 
    </property>
    
  • <props> 是简化后了的,或者说是特殊化的map,该元素配置类型为java.util.Properties的对象依赖。因为Properties只能指定String类型的键和值,所以的配置简化很多

    public class MockDemoObject { 
    	private Properties emailAddrs;
    	// 相应的setter和getter方法 ...
    }
    

    每个可以嵌套多个,内部只能指定字符串

    <property name="valueSet"> 
    	<props> 
    		<prop key="author">fujohnwang@gmail.com</prop> 
    		<prop key="support">support@spring21.cn</prop>
    	</props> 
    </property>
    
  • <null/> 它只是一个空元素,通常用到的场景不是很多。对于String类型来说,如果value注入方式为,那么,得到的结果是"",而不是null。所以,如果需要为某个对象的值注入null,可使用

    public class MockDemoObject { 
     	private Object param2;
    	// 相应的setter和getter方法 ...
    }
    // 这么配置实际上就相当于=null,其实这个配置意义不大
    // private String param1 = null; 
    

    配置:

    <property name="param1"> 
    	<null/>
    </property> 
    

4. depends-on

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

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

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

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

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

5. autowire

  • 除了可以通过配置明确指定bean之间的依赖关系,Spirng还提供了bean的自动绑定功能。通过的autowire属性,可以指定当前bean采用某种类型的自动绑定模式

  • 手工绑定的关系总会覆盖掉自动绑定的

    自动绑定对原生类型、String类型和Classes类型以及这些类型的数组是无效的

  • Spring提供了5种自动绑定模式:no、byName、byType、constructor和autodetect

    • no

      容器默认的自动绑定模式,也就是不采用任何形式的自动绑定,完全依赖手工配置各个bean之间的依赖关系,以下代码演示的两种配置是等效的:

      <bean id="beanName" class="..."/> 
      <bean id="beanName" class="..." autowire="no"/> 
      
    • byName

      按照类中声明的实例变量的名称,与XML配置文件中声明的bean定义的beanName的值(由id属性指定)进行匹配,相匹配的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> 
      
    • byType

      如果bean定义中的autowire=byType,那么容器会根据当前bean定义类型,分析其相应的依赖对象类型,然后到容器所管理的所有bean定义中寻找与依赖对象类型相同的bean定义,然后将找到的符合条件的bean自动绑定到当前bean定义。但如果找到多个,容器解决不了“该选用哪一个”的问题

    • constructor

      byName和byType类型的自动绑定模式是针对property的自动绑定,而constructor类型则是针对构造方法参数的类型而进行的自动绑定。它同样是byType类型的绑定模式,如果找到不止一个符合条件的bean定义,容器会报错

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

      使用上也与byType没有太大差别,只不过constructor是应用到需要使用构造方法注入的bean定义之上,匹配的是构造方法的参数类型,而不是实例属性的类型

      <bean id="fooBean" class="Foo" autowire="constructor"/> 
      <bean id="emphasisAttribute" class="Bar"> 
      </bean>
      
    • autodetect

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

  • 作为所有的统帅,有一个default-autowire属性,它可以帮我们省去为多个单独设置autowire属性的麻烦,default-autowire的默认值为no,即不进行自动绑定

    <beans default-autowire="byType">
        <bean id="..." class="..."/>
        ...
    </beans> 
    

6. dependency-check

可以使用每个的dependency-check属性,检查每个对象某种类型的所有依赖是否全部已经注入完成。可以通过dependency-check指定容器检查特定类型的依赖:

  • none:不做依赖检查,容器以此为默认值
  • simple:对简单属性类型以及相关的collection进行依赖检查,对象引用类型的依赖除外
  • object:只对对象引用类型依赖进行检查
  • all:将simple和object相结合,即对简单属性类型以及相应的collection和所有对象引用类型的依赖进行检查

总地来说,控制得力的话,这个依赖检查的功能我们基本可以不考虑使用

7. 延迟初始化 lazy-init

  • 与BeanFactory不同,ApplicationContext在容器启动的时候,就会马上对所有的“singleton的bean定义”进行实例化操作。可以通过的lazy-init属性来改变某个或者某些bean定义在ApplicationContext容器中的默认实例化时机:

    # 实现ApplicationContext容器在启动时,只实例化not-lazy-init-bean而不实例化lazy-init-bean
    <bean id="lazy-init-bean" class="..." lazy-init="true"/> 
    <bean id="not-lazy-init-bean" class="..."/> 
    
  • 当然,仅指定lazy-init-bean的lazy-init为true,并不意味着容器就一定会延迟初始化该bean的实例。如果某个非延迟初始化的bean定义依赖于lazy-init-bean,那么容器还是会首先实例化lazy-init-bean,然后再实例化后者,导致延迟初始化失败

  • 不过可以通过在顶层由统一控制实现延迟初始化<beans default-lazy-init="true">

三、bean之间的继承关系

除了单独存在的bean以及多个bean之间的横向依赖关系,我们也不能忽略“纵向上”各个bean之间的关系

<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的所有依赖,只需要将特定的属性进行更改,而不要全部又重新定义一遍

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定义不需要实例化,是一个配置模板,不对应任何对象。(加了abstract的Bean,可以不指定class属性)

容器在初始化对象实例的时候,不会关注将abstract属性声明为true的bean定义。如果你不想容器在初始化时实例化某些对象,那么可以将其abstract属性赋值true,以避免容器将其实例化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值