3.3.1  延迟初始化Bean

      延迟初始化也叫做惰性初始化,指不提前初始化Bean,而是只有在真正使用时才创建及初始化Bean。

      配置方式很简单只需在<bean>标签上指定 “lazy-init” 属性值为“true”即可延迟初始化Bean。

      Spring容器会在创建容器时提前初始化“singleton”作用域的Bean,“singleton”就是单例的意思即整个容器每个Bean只有一个实例,后边会详细介绍。Spring容器预先初始化Bean通常能帮助我们提前发现配置错误,所以如果没有什么情况建议开启,除非有某个Bean可能需要加载很大资源,而且很可能在整个应用程序生命周期中很可能使用不到,可以设置为延迟初始化。

      延迟初始化的Bean通常会在第一次使用时被初始化;或者在被非延迟初始化Bean作为依赖对象注入时在会随着初始化该Bean时被初始化,因为在这时使用了延迟初始化Bean。

      容器管理初始化Bean消除了编程实现延迟初始化,完全由容器控制,只需在需要延迟初始化的Bean定义上配置即可,比编程方式更简单,而且是无侵入代码的。

      具体配置如下:


java代码:
  1. <bean id="helloApi"

  2. class="cn.javass.spring.chapter2.helloworld.HelloImpl"

  3. lazy-init="true"/>  

3.3.2  使用depends-on

      depends-on是指指定Bean初始化及销毁时的顺序,使用depends-on属性指定的Bean要先初始化完毕后才初始化当前Bean,由于只有“singleton”Bean能被Spring管理销毁,所以当指定的Bean都是“singleton”时,使用depends-on属性指定的Bean要在指定的Bean之后销毁。


配置方式如下:


java代码:
  1. <bean id="helloApi"class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>  

  2. <bean id="decorator"

  3. class="cn.javass.spring.chapter3.bean.HelloApiDecorator"

  4.    depends-on="helloApi">  

  5.    <property name="helloApi"><ref bean="helloApi"/></property>  

  6. </bean>  


      “decorator”指定了“depends-on”属性为“helloApi”,所以在“decorator”Bean初始化之前要先初始化“helloApi”,而在销毁“helloApi”之前先要销毁“decorator”,大家注意一下销毁顺序,与文档上的不符。

      “depends-on”属性可以指定多个Bean,若指定多个Bean可以用“;”、“,”、空格分割。

      那“depends-on”有什么好处呢?主要是给出明确的初始化及销毁顺序,比如要初始化“decorator”时要确保“helloApi”Bean的资源准备好了,否则使用“decorator”时会看不到准备的资源;而在销毁时要先在“decorator”Bean的把对“helloApi”资源的引用释放掉才能销毁“helloApi”,否则可能销毁 “helloApi”时而“decorator”还保持着资源访问,造成资源不能释放或释放错误。


      让我们看个例子吧,在平常开发中我们可能需要访问文件系统,而文件打开、关闭是必须配对的,不能打开后不关闭,从而造成其他程序不能访问该文件。让我们来看具体配置吧:


1)准备测试类:


ResourceBean从配置文件中配置文件位置,然后定义初始化方法init中打开指定的文件,然后获取文件流;最后定义销毁方法destroy用于在应用程序关闭时调用该方法关闭掉文件流。


DependentBean中会注入ResourceBean,并从ResourceBean中获取文件流写入内容;定义初始化方法init用来定义一些初始化操作并向文件中输出文件头信息;最后定义销毁方法用于在关闭应用程序时想文件中输出文件尾信息。


             具体代码如下:



java代码:
  1. package cn.javass.spring.chapter3.bean;  

  2. import java.io.File;  

  3. import java.io.FileNotFoundException;  

  4. import java.io.FileOutputStream;  

  5. import java.io.IOException;  

  6. publicclass ResourceBean {  

  7. private FileOutputStream fos;    

  8. private File file;  

  9. //初始化方法

  10. publicvoid init() {  

  11.        System.out.println("ResourceBean:========初始化");  

  12. //加载资源,在此只是演示

  13.        System.out.println("ResourceBean:========加载资源,执行一些预操作");  

  14. try {  

  15. this.fos = new FileOutputStream(file);  

  16.        } catch (FileNotFoundException e) {  

  17.            e.printStackTrace();  

  18.        }  

  19.    }  

  20. //销毁资源方法

  21. publicvoid destroy() {  

  22.        System.out.println("ResourceBean:========销毁");  

  23. //释放资源

  24.        System.out.println("ResourceBean:========释放资源,执行一些清理操作");  

  25. try {  

  26.            fos.close();  

  27.        } catch (IOException e) {  

  28.            e.printStackTrace();  

  29.        }  

  30.    }  

  31. public FileOutputStream getFos() {  

  32. return fos;  

  33.    }  

  34. publicvoid setFile(File file) {  

  35. this.file = file;  

  36.    }  

  37. }  



java代码:
  1. package cn.javass.spring.chapter3.bean;  

  2. import java.io.IOException;  

  3. publicclass DependentBean {  

  4.    ResourceBean resourceBean;    

  5. publicvoid write(String ss) throws IOException {  

  6.        System.out.println("DependentBean:=======写资源");  

  7.        resourceBean.getFos().write(ss.getBytes());  

  8.    }  

  9. //初始化方法

  10. publicvoid init() throws IOException {  

  11.        System.out.println("DependentBean:=======初始化");  

  12. resourceBean.getFos().write("DependentBean:=======初始化=====".getBytes());  

  13.    }  

  14. //销毁方法

  15. publicvoid destroy() throws IOException {  

  16.        System.out.println("DependentBean:=======销毁");  

  17. //在销毁之前需要往文件中写销毁内容

  18.        resourceBean.getFos().write("DependentBean:=======销毁=====".getBytes());  

  19.    }  

  20. publicvoid setResourceBean(ResourceBean resourceBean) {  

  21. this.resourceBean = resourceBean;  

  22.    }  

  23. }  


2)类定义好了,让我们来进行Bean定义吧,具体配置文件如下:


java代码:
  1. <bean id="resourceBean"

  2. class="cn.javass.spring.chapter3.bean.ResourceBean"

  3.    init-method="init" destroy-method="destroy">  

  4.    <property name="file" value="D:/test.txt"/>  

  5. </bean>  

  6. <bean id="dependentBean"

  7. class="cn.javass.spring.chapter3.bean.DependentBean"

  8.    init-method="init" destroy-method="destroy" depends-on="resourceBean">  

  9.    <property name="resourceBean" ref="resourceBean"/>  

  10. </bean>  


      <property name="file" value="D:/test.txt"/>配置:Spring容器能自动把字符串转换为java.io.File。


init-method="init" 指定初始化方法,在构造器注入和setter注入完毕后执行。

     destroy-method="destroy"指定销毁方法,只有“singleton”作用域能销毁,“prototype”作用域的一定不能,其他作用域不一定能;后边再介绍。


      在此配置中,resourceBean初始化在dependentBean之前被初始化,resourceBean销毁会在dependentBean销毁之后执行。

      3)配置完毕,测试一下吧:


java代码:
  1. package cn.javass.spring.chapter3;  

  2. import java.io.IOException;  

  3. import org.junit.Test;  

  4. import org.springframework.context.support.ClassPathXmlApplicationContext;  

  5. import cn.javass.spring.chapter3.bean.DependentBean;  

  6. publicclass MoreDependencyInjectTest {  

  7. @Test

  8. publicvoid testDependOn() throws IOException {  

  9.        ClassPathXmlApplicationContext context =  

  10. new ClassPathXmlApplicationContext("chapter3/depends-on.xml");  

  11. //一点要注册销毁回调,否则我们定义的销毁方法不执行

  12.        context.registerShutdownHook();  

  13.        DependentBean dependentBean =  

  14. context.getBean("dependentBean", DependentBean.class);  

  15.        dependentBean.write("aaa");  

  16.    }  

  17. }  


      测试跟其他测试完全一样,只是在此我们一定要注册销毁方法回调,否则销毁方法不会执行。

      如果配置没问题会有如下输出:


java代码:
  1. ResourceBean:========初始化  

  2. ResourceBean:========加载资源,执行一些预操作  

  3. DependentBean:=========初始化  

  4. DependentBean:=========写资源  

  5. DependentBean:=========销毁  

  6. ResourceBean:========销毁  

  7. ResourceBean:========释放资源,执行一些清理操作  


3.3.3  自动装配

      自动装配就是指由Spring来自动地注入依赖对象,无需人工参与。


      目前Spring3.0支持“no”、“byName ”、“byType”、“constructor”四种自动装配,默认是“no”指不支持自动装配的,其中Spring3.0已不推荐使用之前版本的“autodetect”自动装配,推荐使用Java 5+支持的(@Autowired)注解方式代替;如果想支持“autodetect”自动装配,请将schema改为“spring-beans-2.5.xsd”或去掉。


      自动装配的好处是减少构造器注入和setter注入配置,减少配置文件的长度。自动装配通过配置<bean>标签的“autowire”属性来改变自动装配方式。接下来让我们挨着看下配置的含义。


一、default表示使用默认的自动装配,默认的自动装配需要在<beans>标签中使用default-autowire属性指定,其支持“no”、“byName ”、“byType”、“constructor”四种自动装配,如果需要覆盖默认自动装配,请继续往下看;


二、no意思是不支持自动装配,必须明确指定依赖。


三、byName通过设置Bean定义属性autowire="byName",意思是根据名字进行自动装配,只能用于setter注入。比如我们有方法“setHelloApi”,则“byName”方式Spring容器将查找名字为helloApi的Bean并注入,如果找不到指定的Bean,将什么也不注入。

      例如如下Bean定义配置:


java代码:
  1. <bean id="helloApi"class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>  

  2. <bean id="bean"class="cn.javass.spring.chapter3.bean.HelloApiDecorator"

  3.     autowire="byName"/>  


      测试代码如下:



java代码:
  1. package cn.javass.spring.chapter3;  

  2. import java.io.IOException;  

  3. import org.junit.Test;  

  4. import org.springframework.context.support.ClassPathXmlApplicationContext;  

  5. import cn.javass.spring.chapter2.helloworld.HelloApi;  

  6. publicclass AutowireBeanTest {  

  7. @Test

  8. publicvoid testAutowireByName() throws IOException {  

  9. ClassPathXmlApplicationContext context =  

  10. new ClassPathXmlApplicationContext("chapter3/autowire-byName.xml");  

  11.        HelloApi helloApi = context.getBean("bean", HelloApi.class);  

  12.        helloApi.sayHello();  

  13.    }  

  14. }  


      是不是不要配置<property>了,如果一个bean有很多setter注入,通过“byName”方式是不是能减少很多<property>配置。此处注意了,在根据名字注入时,将把当前Bean自己排除在外:比如“hello”Bean类定义了“setHello”方法,则hello是不能注入到“setHello”的。


四、“byType”:通过设置Bean定义属性autowire="byType",意思是指根据类型注入,用于setter注入,比如如果指定自动装配方式为“byType”,而“setHelloApi”方法需要注入HelloApi类型数据,则Spring容器将查找HelloApi类型数据,如果找到一个则注入该Bean,如果找不到将什么也不注入,如果找到多个Bean将优先注入<bean>标签“primary”属性为true的Bean,否则抛出异常来表明有个多个Bean发现但不知道使用哪个。让我们用例子来讲解一下这几种情况吧。


      1)根据类型只找到一个Bean,此处注意了,在根据类型注入时,将把当前Bean自己排除在外,即如下配置中helloApi和bean都是HelloApi接口的实现,而“bean”通过类型进行注入“HelloApi”类型数据时自己是排除在外的,配置如下(具体测试请参考AutowireBeanTest.testAutowireByType1方法):


java代码:
  1. <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>  

  2. <bean id="bean"class="cn.javass.spring.chapter3.bean.HelloApiDecorator"

  3.     autowire="byType"/>  


      2)根据类型找到多个Bean时,对于集合类型(如List、Set)将注入所有匹配的候选者,而对于其他类型遇到这种情况可能需要使用“autowire-candidate”属性为false来让指定的Bean放弃作为自动装配的候选者,或使用“primary”属性为true来指定某个Bean为首选Bean:

      2.1)通过设置Bean定义的“autowire-candidate”属性为false来把指定Bean后自动装配候选者中移除:


java代码:
  1. <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>  

  2. <!-- 从自动装配候选者中去除 -->  

  3. <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"

  4. autowire-candidate="false"/>  

  5. <bean id="bean1"class="cn.javass.spring.chapter3.bean.HelloApiDecorator"

  6.     autowire="byType"/>  


      2.2)通过设置Bean定义的“primary”属性为true来把指定自动装配时候选者中首选Bean:


java代码:
  1. <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>  

  2. <!-- 自动装配候选者中的首选Bean-->  

  3. <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl" primary="true"/>  

  4. <bean id="bean"class="cn.javass.spring.chapter3.bean.HelloApiDecorator"

  5.     autowire="byType"/>  


      具体测试请参考AutowireBeanTest类的testAutowireByType***方法。


五、“constructor”:通过设置Bean定义属性autowire="constructor",功能和“byType”功能一样,根据类型注入构造器参数,只是用于构造器注入方式,直接看例子吧:


java代码:
  1. <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>  

  2. <!-- 自动装配候选者中的首选Bean-->  

  3. <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl" primary="true"/>  

  4. <bean id="bean"

  5. class="cn.javass.spring.chapter3.bean.HelloApiDecorator"

  6.     autowire="constructor"/>  


 测试代码如下:


java代码:
  1. @Test

  2. publicvoid testAutowireByConstructor() throws IOException {  

  3. ClassPathXmlApplicationContext context =  

  4. new ClassPathXmlApplicationContext("chapter3/autowire-byConstructor.xml");  

  5. HelloApi helloApi = context.getBean("bean", HelloApi.class);  

  6. helloApi.sayHello();  

  7. }  


六、autodetect自动检测是使用“constructor”还是“byType”自动装配方式,已不推荐使用。如果Bean有空构造器那么将采用“byType”自动装配方式,否则使用“constructor”自动装配方式。此处要把3.0的xsd替换为2.5的xsd,否则会报错。


java代码:
  1. <?xml version="1.0" encoding="UTF-8"?>  

  2. <beans  xmlns="http://www.springframework.org/schema/beans"

  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  4.        xmlns:context="http://www.springframework.org/schema/context"

  5.        xsi:schemaLocation="  

  6.           http://www.springframework.org/schema/beans

  7.           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

  8.           http://www.springframework.org/schema/context

  9.           http://www.springframework.org/schema/context/spring-context-2.5.xsd">

  10. <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>  

  11.  <!-- 自动装配候选者中的首选Bean-->  

  12.  <bean class="cn.javass.spring.chapter2.helloworld.HelloImpl" primary="true"/>  

  13.  <bean id="bean"

  14. class="cn.javass.spring.chapter3.bean.HelloApiDecorator"

  15.        autowire="autodetect"/>  

  16. </beans>  


      可以采用在“<beans>”标签中通过“default-autowire”属性指定全局的自动装配方式,即如果default-autowire=”byName”,将对所有Bean进行根据名字进行自动装配。

不是所有类型都能自动装配:

  • 不能自动装配的数据类型:Object、基本数据类型(Date、CharSequence、Number、URI、URL、Class、int)等;

  • 通过“<beans>”标签default-autowire-candidates属性指定的匹配模式,不匹配的将不能作为自动装配的候选者,例如指定“*Service,*Dao”,将只把匹配这些模式的Bean作为候选者,而不匹配的不会作为候选者;

  • 通过将“<bean>”标签的autowire-candidate属性可被设为false,从而该Bean将不会作为依赖注入的候选者。

数组、集合、字典类型的根据类型自动装配和普通类型的自动装配是有区别的:

  • 数组类型、集合(Set、Collection、List)接口类型:将根据泛型获取匹配的所有候选者并注入到数组或集合中,如“List<HelloApi> list”将选择所有的HelloApi类型Bean并注入到list中,而对于集合的具体类型将只选择一个候选者,“如 ArrayList<HelloApi> list”将选择一个类型为ArrayList的Bean注入,而不是选择所有的HelloApi类型Bean进行注入;

  • 字典(Map)接口类型:同样根据泛型信息注入,键必须为String类型的Bean名字,值根据泛型信息获取,如“Map<String, HelloApi> map” 将选择所有的HelloApi类型Bean并注入到map中,而对于具体字典类型如“HashMap<String, HelloApi> map”将只选择类型为HashMap的Bean注入,而不是选择所有的HelloApi类型Bean进行注入。


      自动装配我们已经介绍完了,自动装配能带给我们什么好处呢?首先,自动装配确实减少了配置文件的量;其次, “byType”自动装配能在相应的Bean更改了字段类型时自动更新,即修改Bean类不需要修改配置,确实简单了。


      自动装配也是有缺点的,最重要的缺点就是没有了配置,在查找注入错误时非常麻烦,还有比如基本类型没法完成自动装配,所以可能经常发生一些莫名其妙的错误,在此我推荐大家不要使用该方式,最好是指定明确的注入方式,或者采用最新的Java5+注解注入方式。所以大家在使用自动装配时应该考虑自己负责项目的复杂度来进行衡量是否选择自动装配方式。

      自动装配注入方式能和配置注入方式一同工作吗?当然可以,大家只需记住配置注入的数据会覆盖自动装配注入的数据。

      大家是否注意到对于采用自动装配方式时如果没找到合适的的Bean时什么也不做,这样在程序中总会莫名其妙的发生一些空指针异常,而且是在程序运行期间才能发现,有没有办法能在提前发现这些错误呢?接下来就让我来看下依赖检查吧。