Spring in Action 学习笔记 第二章 装配 Bean

 

一、容纳你的 Bean

容器是 Spring 的核心。 Spring 使用 IoC 管理所有组成应用系统的组件。 Spring 有两种不同的容器:

l          Bena 工厂( BeanFactory ):由 org.springframework.beans.factory.BeanFactory 接口定义。是最简单的容器,提供了基础的依赖注入支持。

l          应用上下文 (ApplicationContext) :由 org.springframework.context.ApplicationContext 接口定义。建立在 Bean 工厂之上提供了系统架构服务。

1 BeanFactory 介绍

       见名知意, BeanFactory 采用了工厂模式(抽象工厂模式 http://blog.csdn.net/qutr/archive/2006/01/22/586034.aspx 工厂方法模式 http://blog.csdn.net/qutr/archive/2006/07/21/954070.aspx )。这个类专门负责创建类(对象)。有些书上说 Spring BeanFactory 像一个超负荷运转的机器,因为除了简单的创建对象以外, BeanFactory 可以在实例化这些对象的时候,创建协作对象间的关联关系。这样就把配置的负担从 Bean 自身以及 Bean 的调用者中脱离出来。更详细的可以查看 BeanFactory 的源代码。

Spring 中有几种 BeanFactory 的实现。其中最长用的是 org.springframework.beans.factory.xml.XmlBeanFactory ,它根据 XML 文件中的定义装载 Bean 。在 XmlBeanFactory 类中提供了两个构造函数,通常我们用的是 XmlBeanFactory(Resource resource) throws BeansException 这个。他一般这样定义: BeanFactory factory = new XmlBeanFactory(new FileSystemResource(“beans.xml”)); 其中传递的参数引用为: import org.springframework.core.io.FileSystemResource; 书中这里是不太正确的,因为在 Spring2.0 里已经不提供传递 java.io.InputStream 对象的构造函数了。翻译者非常的不负责任,应该在这里给出说明。

BeanFactory 得到一个 Bean 只需调用 getBean(“beanName”) 方法,把你需要的 Bean 的名字当作参数传递进去就可以了。像这样: MyBean myBean = (MyBean)factory.getBean(“myBean”); getBean() 方法被调用的时候,工厂就会开始实例化 Bean 并且使用依赖注入开始设置 Bean 的属性。

事实上 BeanFactory 接口提供有 6 种方法供客户代码调用:

1 boolean containsBean(String) : 如果 BeanFactory 包含符合给定名称的 Bean 定义或 Bean 实例,则返回 true

2 Object getBean(String) : 返回以给定名字注册的 Bean 实例。就是上面提到的最简单的一种。

3 Object getBean(String, Class) : 返回以给定名称注册的 Bean ,返回的 Bean 将被扔给( cast )给定的类。如果 Bean 不能被 Cast ,相应的异常( BeanNotOfRequiredTypeException )将被抛出。

4 Class getType(String name) : 返回给定名称的 Bean Class 。如果没有相应于给定名称的 Bean ,则抛出 NoSuchBeanDefinitionException 异常。

5 boolean isSingleton(String) : 决定在给定名称时, Bean 定义或 Bean 实例注册是否为单件模式,如果相应于给定名称的 Bean 不能被找到,则抛出 NoSuchBeanDefinitionException 异常。

6 String[] getAliases(String) : 如果在 Bean 的定义中有定义,则返回给定 Bean 名称的别名。

public   interface  BeanFactory  {              //beanfactory的源码


    String FACTORY_BEAN_PREFIX 
=   " & " ;



    Object getBean(String name) 
throws  BeansException;


    Object getBean(String name, Class requiredType) 
throws  BeansException;

    
    
boolean  containsBean(String name);

    
    
boolean  isSingleton(String name)  throws  NoSuchBeanDefinitionException;

        Class getType(String name) 
throws  NoSuchBeanDefinitionException;


    
    String[] getAliases(String name) 
throws  NoSuchBeanDefinitionException;

}

 

2 .使用应用上下文( ApplicationContext

使用 ApplicationContext 可以获得 Sroing 框架的强大功能。表面上 ApplicationContext BeanFactory 差不多,但是 ApplicationContext 提供了更多的功能:

l          ApplicationContext 提供了文本信息解析工具,包括对国际化的支持。

l          ApplicationContext 提供了载入文件资源的通用方法,如载入图片。

l          ApplicationContext 可以向注册为监听器的 Bean 发送事件。

l          ApplicationContext Spring2.0 里可能还加了其他功能。

有三种 ApplicationContext 的实现经常被用到:

l          ClassPathXmlApplicationContext— 从类路径中的 XML 文件载入上下文定义信息,把上下文定义文件当成类路径资源。(可以在整个类路径中寻找 XML 文件)

l          FileSystemXmlApplicationContext— 从文件系统中的 XML 文件载入上下文定义信息。(只能在指定的路径中寻找 XML 文件)

l          XmlWebApplicationContext— Web 系统中的 XMLwenjian 载入上下文定义信息。

其中 FileSystemXmlApplicationContext ClassPathXmlApplicationContext (他们都包含在 org.springframework.context.support 包下)的使用形式分别如下:

ApplicationContext context = new FileSystemXmlApplicationContext(“c:/foo.xml”);

ApplicationContext context2 = new ClassPathXmlApplicationContext(“foo.xml”)

应用上下文会在上下文启动后预载入所有的单实例 Bean 。确保当你需要的时候他们已经准备好了你的应用不需要等待他们被创建。

总之实例化一个 Bean 大概有三种方法:

第一种:

Resource resource = new FileSystemResource("beans.xml");

BeanFactory factory = new XmlBeanFactory(resource);

第二种:

ClassPathResource resource = new ClassPathResource("beans.xml");

BeanFactory factory = new XmlBeanFactory(resource);

第三种:

ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"applicationContext.xml", "applicationContext-part2.xml"});

BeanFactory factory = (BeanFactory) context;

ApplicathionContext 的超类是 BeanFactory ,所以理所当然的这里的 context 可以是 BeanFactory

3 Bean 的生命周期

在书中作者用图文并茂的方式解说了 Bean BeanFactory ApplicationContext 中的生命周期,从创建到销毁的全过程。他们二者之间略有不同,书上讲的比较详细,这里就不罗列了。

 

二、基本装配

Spring 容器内拼凑 Bean 叫做装配。装配 Bean 的时候,你是在告诉容器需要哪些 Bean 以及容器如何使用依赖注入将他们配合在一起。

1 .使用 XML 装配

Bean 装配在 Spring 中最常用的是 XML 文件。在前面提到过的 XmlBeanFactory ClassPathXmlApplicationContext FileSystemXmlApplicationContext XmlWebApplicationContext 都支持用 XML 装配 Bean

Spring 规定了自己的 XML 文件格式,根元素为 <Beans> <Beans> 有多个 <Bean> 子元素。每个 <Bean> 定义了一个 Bean 如何被装载到 Spring 容器中。看下面的名为 test.xml XML 文件:

<? xml version = "1.0" encoding = "UTF-8" ?>

<! DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd" >

< beans >

    < bean id = "foo"

          class = "springinaction.foo" >

    </ bean >

    < bean id = "bar"

          class  = "springinaction.bar" >

    </ bean >

</ beans >

Spring 中,对一个 Bean 的最基本的配置包括 Bean ID 和他的类全称。在 test.xml 文件中其中 <Beans> 为根元素, bean id=”foo” 标明一个 Bean 他的 Id foo ,此处的 foo 是由程序员自己命名的,但是在一个大的系统中有好多个 Bean 这里要求 XML 中的 Id 要唯一,在程序代码中如果想取得 foo 这个 Bean 就要用 getBean(“foo”) 了。 class=”springinaction.foo” Bean 的全称类名。

2 .添加一个 Bean

Spring 中的 Bean 在缺省情况下是单实例模式(也叫单态模式, http://blog.csdn.net/qutr/archive/2006/02/28/611868.aspx )。如果想每次从容器中得到得到一个 Bean 的不同实例,需要将 Bean 定义为原型模式( http://blog.csdn.net/qutr/archive/2006/07/24/968297.aspx )。我们可以用 Bean singleton 属性来设置,缺省情况为 true

< bean id  =   " foo "

          
class   =   " springinaction.foo "

          singleton
= " false " >

    Bean的定义中用init-method来初始化某些参数,用destroy-method来销毁对象。

< bean id  =   " foo "

          
class   =   " springinaction.foo "

          singleton
= " false "

          init
- method = " init "

          destroy
- method = " destory " >

    Spring也提供了两个接口来实现相同的功能:InitializingBeanDisposableBean

3 .通过 Set 方法注入依赖。

写过 JavaBean 的人可能都熟悉 get set 方法。 Spring 就是用 Set 注入来配置 Bean 的。 <bean> 的子元素 <property> 指明了使用它们的 set 方法来注入。你可以注入任何类型。

每个 Bean (通常就是你定义的一个 class )通常都会有一些简单的类型成员,如 int String 等等。通过使用 <property> 的子元素 <value> 可以设置基本类型的属性(值)。

< bean id = "bar" class  = "springinaction.bar" >

        < property name = "barName" >

            < value > coffeeBar </ value >

        </ property >

</ bean >

如上所示,在我们的 springinaction.bar 类中定义了一个名为 barName String 类型的成员,用 < value > coffeeBar </ value > 中的coffeeBar来给他设置值。如:setName( barName ); 如果定义了一个int类型的成员我们可以在<value></value>中写入一个数字。

利用property丰富的属性我们可以为一个Bean引用其它Bean,通过<ref>实现;可以嵌入一个内部Bean,通常很少使用。可以装配各种集合,如 java.util.List, java.util.Set, java.util.Map 等等。可以设置 properties null 值。

下面给出一个例子,这个例子同样来自官方文档。

< bean id = " moreComplexObject "   class = " example.ComplexObject " >

  
<!--  java.util.Properties  -->

  
< property name = " adminEmails " >

    
< props >

        
< prop key = " administrator " > administrator@somecompany.org </ prop >

        
< prop key = " support " > support@somecompany.org </ prop >

        
< prop key = " development " > development@somecompany.org </ prop >

    
</ props >

  
</ property >

  
<!--  java.util.List  -->

  
< property name = " someList " >

    
< list >

        
< value > a list element followed by a reference </ value >

        
< ref bean = " myDataSource "   />

    
</ list >

  
</ property >

  
<!--  java.util.Map  -->

  
< property name = " someMap " >

    
< map >

        
< entry >

            
< key >

                
< value > yup an entry </ value >

            
</ key >

            
< value > just some string </ value >

        
</ entry >

        
< entry >

            
< key >

                
< value > yup a ref </ value >

            
</ key >

            
< ref bean = " myDataSource "   />

        
</ entry >

    
</ map >

  
</ property >

  
<!--  java.util.Set  -->

  
< property name = " someSet " >

    
< set >

        
< value > just some string </ value >

        
< ref bean = " myDataSource "   />

    
</ set >

  
</ property >

</ bean >

一目了然,非常清楚就不多解释了。关于集合Spring2.0又添加了新的内容,如:你如果使用的是JDK1.5那么还可以使用Java的泛型来清晰的解析各种容器所包含的类型,请参看:http://static.springframework.org/spring/docs/2.0.x/reference/beans.html#beans-collection-elements

4 .通过构造函数注入依赖

Set 注入是 Srping 所推荐的,但是 Set 注入的缺点是,他无法清晰的表示出哪些属性是必须的,哪些是可选的。而使用构造函数注入的优势是通过构造函数来强制以来关系,有了构造函数的约束不可能实现一个不完全或无法使用的 Bean

< bean id = " coo "   class = " springinaction.coo " >

        
< constructor - arg >

            
< value > cool </ value >

        
</ constructor - arg >

    
</ bean >

上面的例子通过构造函数传值来实例化一个coo对象。如coo co = new coo(“cool”);

如果有多个构造函数,那么可以设置 < constructor-arg > typeindex来传入参数了如果类型都一样那么只能用index了,index的值是从0开始的。

Spring 为我们提供了Set注入和构造函数注入,在两者的使用方式上个人有个人的理由和不同见解,在本书中作者给我们的建议大概是:看情况,那个合适用哪个(废话 J )。但是, Spring 的开发团队通常提倡人们使用 setter 注入。 其实Spring一共给我提供了三种注入方式:接口注入和上面的两种注入。他们都有各自的优点,详细的说明见《Spring开发指南》。

下面再给出两个例子来说明一下Setter注入和constructor注入:

< bean id = " exampleBean "  

        
class = " examples.ExampleBean " >

    

     
<!--  用嵌套的  < ref />  元素进行setter注入  -->

    
< property name = " beanOne " >

        
< ref bean = " anotherExampleBean " />

    
</ property >

    

    
<!--  用嵌套的  ' ref '  属性进行setter注入 (注意:如果你看得是翻译的2.0的预览版这里是错误的,请和官方的英文版对照看) -->

    
< property name = " beanTwo "  ref  =   " yetAnotherBean " /><! —我这里是正确的, 2 .0的中文预览版这里写错了 -->

    

      
< property name = " integerProperty "  value = " 1 " />

    
</ bean >

 

    
< bean id = " anotherExampleBean "   class = " examples.AnotherBean " />

    
< bean id = " yetAnotherBean "   class = " examples.YetAnotherBean " />

Spring提供了快捷的ref属性,来代替ref元素,但是使用ref属性你要小!

下面是一个类

public   class  ExampleBean

{

 

    
private  AnotherBean beanOne;

    
private  YetAnotherBean beanTwo;

    
private   int  i;

 

    
public   void  setBeanOne(AnotherBean beanOne)  {

        
this .beanOne  =  beanOne;

    }


 

    
public   void  setBeanTwo(YetAnotherBean beanTwo)  {

        
this .beanTwo  =  beanTwo;

    }


 

    
public   void  setIntegerProperty( int  i)  {

        
this .i  =  i;

    }
    

}


上面是一个Bean(其实就是一个普通的不能在普通的Java类)和它相对应的XML的配置文件,是一个典型的setter注入。例子清晰易懂就不多说了。

< bean id = " exampleBean "  

        
class = " examples.ExampleBean " >

    

    
<!--  用嵌套的  < ref />  元素进行构造函数注入  -->

    
< constructor - arg >

        
< ref bean = " anotherExampleBean " />

    
</ constructor - arg >

    

    
<!--  用嵌套的  ' ref '  属性进行构造函数注入  -->

    
< constructor - arg ref = " yetAnotherBean " />

      

    
< constructor - arg type = " int "  value = " 1 " />

    
</ bean >

    

    
< bean id = " anotherExampleBean "   class = " examples.AnotherBean " />

    
< bean id = " yetAnotherBean "   class = " examples.YetAnotherBean " />

下面是一个类

public   class  ExampleBean

{

    
private  AnotherBean beanOne;

    
private  YetAnotherBean beanTwo;

    
private   int  i;

    

    
public  ExampleBean(AnotherBean anotherBean, YetAnotherBean yetAnotherBean,  int  i)

    
{

        
this .beanOne  =  anotherBean;

        
this .beanTwo  =  yetAnotherBean;

        
this .i  =  i;

    }


}


上面是一个Bean和它相对应的XML的配置文件,是一个典型的constructor注入。例子也比较清晰易懂这里也就不多说了。

 

三、自动装配

在前面讲到的都是手动显示装配 Bean 的方法, Spring 还提供了自动装配 Bean 的方法。只要设置 <Bean> autowire 属性。有四种自动装配类型:

l          byName :在容器中寻找和需要自动装配的属性名相同的 Bean (或 ID )。如果没有找到这个属性就没有装配上。

l          byType :在容器中寻找一个与自动装配的属性类型相同的 Bean 。如果没有找到这个属性就没有装配上。如果找到超过一个则抛出 org.springframework.beans.factory.UnsatisfiedDependencyException 异常。

l          constructor :在容器中查找与需要自动装配的 Bean 的构造函数参数一致的一个或多个 Bean 。如果存在不确定 Bean 或构造函数容器会抛出 org.springframework.beans.factory.UnsatisfiedDependencyException 异常。

l          autodetect :首先尝试使用 constuctor 来自动装配,然后使用 byType 方式。不确定性的处理与 constructor 方式和 byType 方式一样,都抛出 org.springframework.beans.factory.UnsatisfiedDependencyException 异常。

如下实例:

在前面的显示装配:

< bean id = " courseService "

        
class = " springinaction.chapter02.CourseServiceImpl " >

        
< property name = " courseDao " >

            
< ref bean = " courseDao " />

        
</ property >

        
< property name = " studentService " >

            
< ref bean = " studentService " />

        
</ property >

    
</ bean >

下面是自动装配:

< bean id = " courseService "

        
class = " springinaction.chapter02.CourseServiceImpl "

        autowire
= " byName " />

SpringBean自动装配的过程中很容易出现不确定性,这些不确定性会导致程序无法运行。那么在我们的实际应用中要避免出现装配的不确定性。避免装配的不确定性的最好的方法就是使用显示装配和自动装配的混合方法。对有二义性的Bena使用显示装配,对没有二义性的Bean使用自动装配。

 

在通常情况下我们会分门别类的吧 Bena 设置在多个 XML 文件中。另外一种方法是使用一个或多个的 <import/> (就像我们写 Java 程序要引入必要的包下的类一样)元素来从另外一个或多个文件加载 Bean 定义。需要注意的是:任何 <import/> 元素必须放在配置文件中的 <bean/> 元素之前以完成 Bean 定义的导入。如下示例:

<beans>

<import resource="services.xml"/>

<import resource="resources/messageSource.xml"/>

<import resource="/resources/themeSource.xml"/>

<bean id="bean1" class="..."/>

<bean id="bean2" class="..."/>

<beans/>

上例中,三行 import 引入了必要的 XML 文件。两行 bean 定义了 Bean 依照 XML Schema DTD ,被导入文件必须是完全有效的 XMLBean 定义文件,且包含上层的 <beans/> 元素。

 

四、使用 Spring 的特殊 Bean 和应用举例

       在书中谈到的 Spring 的特殊 Bean 的使用方法,我个人觉得这时 Spring IoC 的高级用法或者是边角用法,由于我也是新手初学这里就不多啰嗦了,等以后在实践中有了体会在补上,关于这方面的内容在 Spring2.0 的官方文档上都有提到,这份文档非常的详细,我个人觉得只要把这份文档好好看了其他的书甚至可以放在一边了,当然 Rod 写的书还是要好好看的。

       关于应用举例,本书中在这章举的例子不是很全面,刚开了个头后面感觉就不说了。我本来想将我最近正做的一个实际应用中的例子放到这里,但是我的这个程序好像有点为了 Spring Spring 所以让我改了又改,至今也没有出来所以也就不再这里显示了。等以后专门写一篇文章来讨论一下。

 

五、小结

       Spring 框架的核心是 Spring 容器。 BeanFactory 是最简单的容器,他提供了最基本的依赖注入和 Bean 装配服务。但是,毕竟 Spring 是一个轻量级框架,要想得到更多的高级框架服务时我们需要使用 ApplicationContext 容器,所以为了让你的应用程序看起来更高级更上档次尽量多的使用 ApplicationContext ,这也是本书作者所提倡的。

其实关于 Spring IoC 的核心和大部分内容在上面的文字中已经提到了,说来说去表面上就那些东西了。而 IoC DI )的真正思想是要靠大量的实践去体会和领悟的。