Spring <bean> 之间的关系&整合多个配置文件

Spring 不但可以通过 <ref>  引用另一个 Bean,建立起 Bean 和 Bean 之间的依赖关系,<bean> 元素标签之间也可以建立类似的关系,完成一些特殊的功能。

1.继承

OOP思想告诉我们,如果多个类拥有相同的方法和属性,则可以引入一个父类,在父类中定义这些类共同的方法和属性,以消除重复的代码。同样,如果多个 <bean> 存在相同的配置信息,则 Spring 允许定义一个父<bean>,子<bean>将自动继承父<bean>的配置信息。

下面通过一个实例,对使用和未使用父子<bean>的配置进行比较,从中看出父子<bean>给配置带来的便利性,如下所示。

未使用父子<bean>的配置

<bean id="car1" class="com.smart.tagdepend.Car"
    p:brand="红旗CA72" p:price="2000.00" p:color="黑色" />
<bean id="car2" class="com.smart.tagdepend.Car"
    p:brand="红旗CA72" p:price="2000.00" p:color="红色" />

上面代码配置了两个 car Bean,我们发现这两个 Bean 的配置存在大量的重复信息。事实上,二者除了 color 属性配置值不一样外,其他配置信息都相同。通过父子<bean>的继承关系就可以很好地消除这种重复的配置信息如下所示。

使用父子<bean>的配置

<!-- ①定义为抽象<bean> -->
<bean id="abstractCar" class="com.smart.tagdepend.Car"
  p:brand="红旗CA72" p:price="2000.00" p:color="黑色" abstract="true"/>
<!-- ②继承于abstractCar -->          
<bean id="car3" parent="abstractCar">
  <property name="color" value="红色"/>
</bean>
<!-- ③继承于abstractCar -->    
<bean id="car4" parent="abstractCar" >
  <property name="color" value="白色"/>
</bean>    

car3 和 car4 这两个 <bean> 都继承于 abstractCar 的 <bean>,Spring 会将父<bean>的配置信息传递给子<bean>。如果子<bean>提供了父<bean>已有的配置信息,那么子<bean>的配置信息将覆盖父<bean>的配置信息。

父<bean>的主要功能是简化子<bean>的配置,所以一般声明为 abstract="true",表示这个<bean>不实例化为一个对应的Bean。如果用户没有指定 abstract="true",则 Spring IOC容器会实例化一个名为 abstractCar 的 Bean。

2.依赖
一般情况下,可以使用 <ref> 元素标签建立对其他 Bean 的依赖关系,Spring 负责管理这些 Bean 的关系。当实例化一个 Bean 时,Spring 保证该 Bean 所依赖的其他 Bean 已经初始化。

但在某些情况下,这种 Bean 之间的依赖关系并不那么明显。下面举一个例子。“小春论坛”拥有很多系统参数(如会话过期时间、缓存更新时间等),这些系统参数用于控制系统的运行逻辑。我们用一个 SystemSettings 类表示这些系统参数。

public class SystemSettings {
   public static int SESSION_TIMEOUT = 30;
   public static int REFRESH_CYCLE = 60;
}

在 SystemSettings 类中为每个系统参数提供了默认值,但一个灵活的论坛必须提供一个管理后台,在管理后台中可以调整这些系统参数并保存到后台数据库中,在系统启动时,初始化程序从数据库后台加载这些系统参数的配置值以覆盖默认值。

public class SysInit {
   public SysInit(){  
       System.out.println("SysInit");
       //模拟从数据库中加载系统设置信息
       SystemSettings.REFRESH_CYCLE = 100;
       SystemSettings.SESSION_TIMEOUT = 10;
   }
}

假设论坛有一个缓存刷新管理器,它需要根据系统参数 SystemSettings.REFRESH  CYCLE创建缓存刷新定时任务。

public class CacheManager {
   public CacheManager(){
       Timer timer = new Timer();
       TimerTask cacheTask = new CacheTask();
       timer.schedule(cacheTask,0,SystemSettings.REFRESH_CYCLE*1000);  
   }
}

在以上实例中,CacheManager 依赖于 SystemSettings,而 SystemSettings 的值由 Syslnit 负责初始化。虽然 CacheManager 不直接依赖于 Syslnit,但从逻辑上看,CacheManager 希望在 Syslnit 加载并完成系统参数设置后再启动,以避免调用不到真实的系统参数值。如果这3个 Bean 都在 Spring 配置文件中定义,那么如何保证 Syslnit 在 CacheManager 之前进行初始化呢?

Spring 允许用户通过 depends-on 属性显式指定 Bean 前置依赖的 Bean,前置依赖的 Bean 会在本 Bean 实例化之前创建好。

<!-- <bean>的依赖 -->
<bean id="cacheManager" class="com.smart.tagdepend.CacheManager" depends-on="sysInit" />
<bean id="sysInit" class="com.smart.tagdepend.SysInit" />

通过 depends-on 属性将 syslnit 指定为 manager 前置依赖的 Bean,这样就可以保证 managerBean 在实例化并运行时所引用的系统参数是最新的设置值,而非 SystemSettings 类中的默认值。如果前置依赖于多个 Bean,则可以通过逗号、空格或分号的方式创建 Bean 的名称。

3.引用

假设一个<bean>要引用另一个<bean>的id属性值,则可以直接使用以下配置方式:

<!-- <bean>引用 -->
<bean id="car" class="com.smart.tagdepend.Car"/> ①
<bean id="boss" class="com.smart.tagdepend.Boss" >  ②
  <property name="carId"  >
    <value>car</value>
  </property>
</bean>

假设希望将 boss Bean 的 carld 设置为①处 <bean> 的 id 值,虽然可以通过②处的方式以字面值的形式进行设置,但二者之间并没有建立引用关系。一般情况下,在一个 Bean 中引用另一个 Bean 的 id 是希望在运行期通过 getBean(beanName) 方法获取对应的 Bean。由于 Spring 并不会在容器启动时对属性配置值进行特殊检查,因此,即使编写错误,也需要等到具体调用时才会发现。

Spring 为此提供了一个 <idref> 元素标签,可以通过 <idref> 引用另一个 <bean> 的名字。在容器启动时,Spring 负责检查引用关系的正确性,这样就可以提前发现错误。因此,下面的配置是推荐的优化方案:

<bean id="car" class="com.smart.tagdepend.Car"/><bean id="boss" class="com.smart.tagdepend.Boss" >  ②
    <idref bean="car"/>
  </property>
</bean>

假设②处由于配置错误,误将 <idref bean="car"/> 写为 <idref bean="cat"/>,那么 Spring 容器在启动时,将会抛出 BeanDefinitionStoreException,提示容器中没有名为 cat 的 Bean

如果引用者和被引用者的 <bean> 位于同一个XML配置文件中,则可以使用 <idref local="car"> 的配置方式,这时IDE的XML分析器就可以在开发期发现引用错误了。

 

整合多个配置文件

对于一个大型应用来说,可能存在多个 XML 配置文件,在启动 Spring 容器时,可以通过一个 String 数组指定这些配置文件。Spring 还允许通过 <import> 将多个配置文件引入到一个文件中,进行配置文件的集成。这样,在启动 Spring 容器时,仅需指定这个合并好的配置文件即可。

<import resource="classpath:com/smart/impt/beans1.xml"/> ①
<bean id="boss1" class="com.smart.fb.Boss" p:name="John" p:car-ref="car1"/>
<bean id="boss2" class="com.smart.fb.Boss" p:name="John" p:car-ref="car2"/>

假设已经在 beans1.xml 中配置了 car1 和 car2 的 Bean,在①处通过 <import> 的 resource 属性引入 beans1.xml,beans2.xml 就拥有了完整的配置信息,Spring 容器仅需通过 beans2.xml 就可以加载所有的配置信息。

需要指出的是,如果一个配置文件 a.xml 定义的 <bean> 引用了另一个配置文件 b.xml 定义的 <bean>,那么并不一定需要通过 <import> 引入 b.xml,只需在启动 Spring 容器时,a.xml 和 b.xml 都在配置文件列表中即可。区别在于,如果 a.xml 采用 import 引入了 b.xml,相当于 a.xml 一个文件就包含了 a.xml 和 b.xml 两个文件的内容,因此 Spring 容器启动时仅需加载 a.xml 即可:否则就需要在启动 Spring 容器时,同时加载 a.xml 和 b.xml 配置文件,以便在内存中对 a.xml 和 b.xml 进行合并。

一个 XML 配置文件可以通过 <import> 组合多个外部的配置文件,resource 属性支持 Spring 标准的资源路径。

 

posted @ 2019-06-23 14:09  认真对待世界的小白  阅读(1088)  评论(0编辑  收藏  举报