<2> 基本bean装配

本篇只介绍基本的最常用的bean装配知识,高级装配在下篇简要梳理。

在spring中,组件无需自己负责与其他组件的关联,取而代之的是,容器负责把协作组件的引用给予各个组件。创建系统组件之间协作关系的动作是DI的关键,通常被称之为装配。

Spring容器是Spring框架的核心,容器可以创建组件,装配和配置组件,以及管理他们的生命周期(从new()到finalize())。
Spring提供了多种容器实现,并分为两类。一类是Bean工厂(BeanFactory),最简单的容器,提供了基础的依赖注入支持。一类是应用上下文(ApplicationContext),建立在Bean工厂基础之上,提供了系统架构服务,如从属性文件中读取文本信息,向相关的事件监听器发布事件(这两点在下篇bean的高级装配中都会有)。

Spring中有几种BeanFactory的实现,用的最多的是XmlBeanFactory。要使用XmlBeanFactory需要传递一个org.srpringframework.core.io.Resource实例给构造函数,此Resource对象提供xml文件给容器。Spring提供的Resource有下面几种:



如:
BeanFactory factory = new XmlBeanFactory(new FileSystemResource(“c:/bean.xml”));
这行简单的代码告诉Bean工厂从xml文件中读取Bean的定义信息,但是,现在工厂还没有实例化Bean,Bean工厂是延迟加载bean的,只有在需要的时候才加载(后面会看到应用上下文会预加载singleton的bean,除非bean被声明了lazy-init=”true” 或abstract=”true”)。
为了从BeanFactory得到一个Bean,只要简单的调用getBean()方法,把bean的名字当参数传递进去就行了。
MyBean bean = (MyBean)factory.getBean(“myBeanName”);
当调用getBean()方法时,工厂才会实例化Bean,并且使用依赖注入设置bean的属性,这样就在Spring容器中开始了Bean的生命。

由于应用上下文提供了更强的功能,几乎所有的应用系统都选择ApplicationContext而不是BeanFactory.只在资源很少的情况下才会考虑采用BeanFactory,如移动设备上。
在ApplicationContext的诸多实现中,有三个实现经常用到:
1)ClassPathXmlApplicationContext,从类路径中的xml文件载入上下文定义信息。
2)FileSystemXmlApplicationContext, 从文件系统中的xml文件载入上下文信息。
3)XmlWebApplicationContext,从web系统中的xml文件载入上下文信息。

使用举例:
ApplicationContext ctx = new ClassPathXmlApplicationContext(“applicationContext.xml”);

ApplicationContext ctx = new FileSystemXmlApplicationContext(“c:/applicationContext.xml”);
两者区别是FileSystemXmlApplicationContext只能在指定的路径中寻找applicationContext.xml文件,而ClassPathXmlApplicationContext可以在整个类路径(包括jar文件)中寻找xml文件。
然后可以向BeanFactory一样通过getBean()获取bean,因为ApplicationContext接口扩展自BeanFactory接口。
应用上下文和bean工厂的另一个重要区别是:Bean工厂延迟加载所有的bean,直到getBean()被调用才创建bean,而应用上下文启动后会预载入所有的单实例bean, 除非bean被声明了lazy-init=”true” 或abstract=”true”.

下图是Bean在BeanFactory中的生命周期:



Bean在应用上下文中的生命周期跟上面只有一点不同:



唯一的不同是,如果bean实现了ApplicationContextAware接口,setApplicationContext()方法会被调用。

在xml中声明bean.
<bean id=”duke” class=”com.spring.Juggler” />
<bean>元素是Spring中最基本的配置单元,它通知spring创建一个bean对象。当spring容器装载该bean时,将用默认的构造函数实例化。
其中,id属性定义了该bean的名称,class属性指明了bean的类型。还可以定义:
<bean name=”duke” class=”com.spring.Juggler” />

注:bean的id和name的区别
1)id必须符合xml命名规范,不能以数字,符号开头,不能有空格。name无限制。
2)id不能有重复,否则初始化会报错,name可以重复,后面的会覆盖前面的。
3)name可以用逗号(,)隔开,每个名称都可用,如:
   <bean name=”aa,bb,cc” class=”…”  />
   则:getBean(“aa”); 或getBean(“bb”);或getBean(“cc”)都一样。
4)id和name都不指定,则以类全名为name.如果有多个不指定name和id的匿名bean,且类名一样,如:
<bean class=”com.spring.Test” />
<bean class=”com.spring.Test” />
<bean class=”com.spring.Test” />
则:getBean(“com.spring.Test”);取的是第一个,
getBean(“com.spring.Test#1”);取的是第二个,
getBean(“com.spring.Test#2”);取的是第三个。
综上:建议使用id,不要使用name.


Bean的属性注入方式:
1. 构造函数注入(使用constructor-arg)
如:
<bean id=”aa” class=”…”>
  <constructor-arg  value=”15” />  ----int型的15,基本类型可以自己转换,使用value设置。
  <constructor-arg  value=”string-15” /> ----string类型的15,基本类型可以自己转换,使用value设置。
<constructor-arg  ref=”otherbean” /> ----使用ref引用其他定义好的bean.
</bean>

注:与ref类似还可以使用idref属性来指定要引用的bean的名称,但两者完全不一样,idref提供的是类似value的功能,只是引用的普通字符串,但是有校验功能,而ref是引用的真实的bean,但idref比value多了验证功能。此外,idref和ref都有三个属性,分别是parent,local和bean。
还可以参考:http://taoyang168.blog.163.com/blog/static/126505069201232533626413/

有些时候,容器在加载XML配置的时候,因为某些原因,无法明确配置项与对象的构造方法参数列表的一一对应关系,就需要 请<constructor-arg>的type或者index属性出马。比如,对象存在多个构造方法,当参数列表数目相同而类型不同的时 候,容器无法区分应该使用哪个构造方法来实例化对象,或者构造方法可能同时传入最少两个类型相同的对象。
如:
<bean id="mockBO" class="..MockBusinessObject">
<constructor-arg type="int">
    <value>111111</value>
</constructor-arg>
</bean>

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

2. setter注入
如:
<bean id=”aa” class=”…” >
<property name=”song” value=”lalala” />
<property name=”instrument” ref=”piano” />
</bean>
注:Spring容器返回bean实例的过程:初始化主调bean->初始化依赖bean->将依赖bean注入到主调bean中->返回完整的主调bean实例。
还可以参考:http://blog.csdn.net/jbuilder3/article/details/5413300

注入内部bean:
<bean id=”aa” class=”…” >
<property name=”song” value=”lalala” />
<property name=”instrument”>
<bean class=”org.srping.Saxophone” />
    </property>
</bean>
内部bean也可以在构造函数注入方式中使用。内部bean只能用来注入,不能用来被其他bean引用。

3. 工厂方法注入(factory-method,允许使用指定的static方法来创建实例而不是使用构造函数)
在后面高级bean装配时会介绍。
还可以参考:
http://blog.sina.com.cn/s/blog_5f1fe33f0100hyx7.html
http://www.360doc.com/content/07/0327/13/18042_416050.shtml

装配集合
前面介绍了基本属性(通过value)和其他类(通过ref),但value和ref只有在属性是单一的时候才有效,当属性是复数(就是集合)时spring提供了4个类型的集合配置元素
<list> 装配一列值,允许重复
<set> 装配值集,确保无重复
<map> 装配键值对的集合,名称和值可以是任意类型
<props> 装配键值对的集合,名称和值都是string类型
<list>和<set>可以不经修改而使用任何collection上,两个元素可以与任意类型java.util.Collection中的属性交换。
如:
<property name=”instruments”>
<list>
    <value>第一个元素</value>
    <ref bean=”guitar” />
<ref bean=”cymbal” />
<ref bean=”harmonica” />
</list>
</property>
<list>可以用在以下代码上照常工作:
java.util.List<Instrument> instruments;  或者
Instruments[ ] instruments; 或者
Collection<Instrument> instruments;

可以在用到<list>的地方换成<set>,区别是<set>可以保证元素唯一。 即使配置了重复元素,使用时仍是保持无重复的元素存在。

<property name=”instruments”>
<map>
    <entry key=”guitar” value-ref=”guitar” />
<entry key=”cymbal” value-ref=”cymbal” />
</map>
</property>
每个entry有两个属性,一个是key,另一个是value.分别有两个属性可以用:
key 指定map的键为string
key-ref 指定键为其他bean的引用
value 指定map的值为string
value-ref 指定map的值为其他bean的引用

<property name=”instruments”>
<props>
    <prop key=”guitar”>I am guitar.</prop>
<prop key=”cymbal”>I am cymbal.</prop>
</props>
</property>

还可以显式的给属性装配null值.如:
<property name=”properynull”><null/></property>
区别:
bean.setMail(“”)  -> <value></value>
bean.setMail(null)  -> <value><null/></value>

自动装配的4种方式:
1) byName 在容器中寻找和需要自动装配的属性名相同的bean,如果没找到则没有被装配上。
2) byType 在容器中寻找一个与需要自动装配的属性类型相同(子类型可以自动向上转型为父类型)的bean,如果没找到则没有被装配。如果找到超过一个类型相同的bean,则会抛出UnsatisfiedDependencyException异常。
3) constructor 在容器中查找与需要自动装配的bean的构造函数参数一致的一个或多个bean,如果窜在不确定的bean或构造函数,则抛出UnsatisfiedDependencyException异常。
4) autodetect 首先尝试使用constructor来自动装配,然后使用byType方式。

如下的配置
<bean id=”kenny” class=”…” >
<property name=”song” value=”Jingle” />
<property name=”instrument” ref=”saxophone” />
</bean>
可以使用自动装配的写法:
<bean id=”instrument” class=”com.Instrument” />
<bean id=”kenny” class=”…” autowire=”byName”>
<property name=”song” value=”Jingle” />
</bean>

Spring的bean有一个 autowire的属性,它可以为以下的6个值:
1、No:即不启用自动装配。Autowire默认的值。 
2、 byName: 通过属性的名字的方式查找JavaBean依赖的对象并为其注入。比如说类Computer有个属性printer,指定其autowire属性为 byName后,Spring IoC容器会在配置文件中查找id/name属性为printer的bean,然后使用Seter方法为其注入。 
3、 byType: 通过属性的类型查找JavaBean依赖的对象并为其注入。比如类Computer有个属性printer,类型为Printer,那么,指定其 autowire属性为byType后,Spring IoC容器会查找Class属性为Printer的bean,使用Seter方法为其注入。 
4、 constructor:通byType一样,也是通过类型查找依赖对象。与byType的区别在于它不是使用Seter方法注入,而是使用构造子注入。 
5、 autodetect:在byType和constructor之间自动的选择注入方式。 
6、 default:由上级标签<beans>的default-autowire属性确定 

一般都是使用byName。
注:自动装配可以减少配置xml,但最大的缺点就是缺乏透明,隐藏了很多细节。建议使用手动装配。
使用<property>和<constructor-arg>配置总会覆盖自动装配的bean。

关于注解Autowired和Resource的作用和区别,源码级分析非常好:
http://www.iflym.com/index.php/code/201211070001.html

Bean的范围(scope)



Bean的初始化和销毁

在spring 容器初始化 bean 和销毁前所做的操作定义方式有三种:
第一种:通过@PostConstruct 和 @PreDestroy 方法实现 初始化之后和销毁bean之前进行的操作。这是一种注解的写法。
第二种是:通过 在xml的bean定义中使用init-method 和  destory-method方法。还可以在<beans>的顶层配置中使用default-init-method和default-destroy-method来指定一个上下文定义文件中所有bean的初始化方法和销毁方法。
如:<bean id="personService" class="com..beanscope.PersonService" scope="singleton"  init-method="init"  destroy-method="cleanUp" /> 
注:非singleton的bean即使指定destroy-method也不会执行,对于prototype类型的bean一旦bean生成之后,spring容器就不管它的消亡了。
第三种是: 通过java bean实现InitializingBean和 DisposableBean接口,要分别实现afterPropertiesSet()方法和destroy()方法。这种通过实现接口的方式与spring框架存在耦合性,建议使用前两种方式。
这三种方式如果同时存在,则一个bean初始化执行顺序是:
Bean的构造函数Construct->@PostConstruct->InitializingBean->init-method.

可以使用init-method和destroy-method属性来指定bean初始化和销毁要做的动作。

此外,上面所有的xml配置都可以使用注解来完成,可以参考:
http://developer.51cto.com/art/201104/255395.htm

猜你喜欢

转载自zoroeye.iteye.com/blog/1922976