查看文章
 
lazy-init属性和预实例化
2010-03-29 10:43
lazy-init属性和预实例化

在 IoC 容器的初始化过程中,主要的工作是对 BeanDefinition 的定位、载入、解析和注册。此时依赖注入并没有发生,依赖注入发生在应用第一次向容器索要 Bean 时。向容器索要Bean是通过getBean的调用来完成的,这个getBean是容器提供Bean服务的最基本的接口。对于容器的初始化也有一种例外情况,就是用户可以通过设置 Bean 的 lazy-init 属性来控制预实例化的过程。这个预实例化在初始化容器时完成 Bean 的依赖注入,毫无疑问,这种容器的使用方式会对容器初始化的性能有一些影响,但却能够提高应用第一次取得 Bean 的性能。因为应用在第一次取得Bean时,依赖注入已经结束了,应用可以取到现成的Bean。

我们回头看看在上下文的初始化过程中,也就是 refresh 中的代码实现,可以看到预实例化是整个refresh初始化IoC容器的一个步骤。在AbstractApplicationContext中看看这个refresh 方法的实现,这个初始化的过程在前面分析 IoC 容器初始化时已经分析过,只不过是从载入和注册BeanDefinition的角度进行分析的。

下面,我们将从 lazy-init 属性配置实现的角度进行分析。对这个属性的处理也是容器refresh 的一部分,在 finishBeanFactoryInitialization 的方法中,封装了对 lazy-init 属性的处理,实际的处理是在 DefaultListableBeanFactory 这个基本容器的preInstantiateSingletons 方法完成的。这个方法对单件 Bean 完成预实例化,这个预实例化的完成巧妙地委托给容器来实现。如果需要预实例化,那么就直接在这里采用 getBean 去触发依赖注入,与正常依赖注入的触发相比,只有触发的时间和场合不同。在这里,依赖注入发生在容器 refresh 的过程中,也就是发生在 IoC 容器初始化的过程中,而不像一般的依赖注入是发生在IoC 容器初始化完成以后,第一次向容器 getBean 时。具体的实现脉络清晰而简洁,如代码清单2-31所示。

代码清单2-31 refresh中的预实例化

  1. public void refresh() throws BeansException,
  2. IllegalStateException {
  3. synchronized (this.startupShutdownMonitor) {   // Prepare
  4. this context for
  5. refreshing.    prepareRefresh();   // Tell the subclass to
  6. refresh the internal
  7. bean factory.    ConfigurableListableBeanFactory beanFactory
  8. =
  9. obtainFreshBeanFactory();   // Prepare the bean factory for
  10. use in this context.
  11. prepareBeanFactory(beanFactory);   try {    // Allows
  12. post-processing of the
  13. bean factory in context subclasses.   
  14. postProcessBeanFactory(beanFactory);  
  15. // Invoke factory processors registered as beans in the
  16. context.  
  17. invokeBeanFactoryPostProcessors(beanFactory);    // Register
  18. bean processors
  19. that intercept bean creation.   
  20. registerBeanPostProcessors(beanFactory);    //
  21. Initialize message source for this context.   
  22. initMessageSource();    //
  23. Initialize event multicaster for this context.  
  24. initApplicationEventMulticaster();    // Initialize other
  25. special beans in
  26. specific context subclasses.     onRefresh();    // Check for
  27. listener beans and
  28. register them.     registerListeners();    // Instantiate all
  29. remaining
  30. (non-lazy-init) singletons.    //
  31. 这里是对lazy-init属性进行处理的地方。  
  32. finishBeanFactoryInitialization(beanFactory);    // Last
  33. step: publish
  34. corresponding event.     finishRefresh();}   catch
  35. (BeansException ex) {     //
  36. Destroy already created singletons to avoid dangling
  37. resources.  
  38. destroyBeans();    // Reset 'active' flag.   
  39. cancelRefresh(ex);    // Propagate
  40. exception to caller.    throw ex;    } } }
  41. //我们到finishBeanFactoryInitialization中去看一下具体的处理
  42. 过程: protected void
  43. finishBeanFactoryInitialization(ConfigurableListableBeanFact
  44. ory beanFactory) {
  45. // Stop using the temporary ClassLoader for type matching.
  46. beanFactory.setTempClassLoader(null);  // Allow for caching
  47. all bean definition
  48. metadata, not expecting further changes.
  49. beanFactory.freezeConfiguration();
  50. // Instantiate all remaining (non-lazy-init) singletons.
  51. /**   *
  52. 这里调用的是BeanFactory的 preInstantiateSingletons,这个方法
  53. *是由DefaultListableBeanFactory实现的。   */
  54. beanFactory.preInstantiateSingletons(); }
  55. //在DefaultListableBeanFactory中的preInstantiateSingletons是
  56. 这样的: public void
  57. preInstantiateSingletons() throws BeansException {  if
  58. (this.logger.isInfoEnabled()) {  
  59. this.logger.info("Pre-instantiating singletons
  60. in " + this);   }
  61. //在这里就开始去getBean了,也就是去触发bean的依赖注入。   /**
  62. *这个getBean和在上面分析的触发依赖注入的过程是一样的,只是发
  63. 生的地方不同。
  64. *如果不设置lazy-init属性,那么这个依赖注入是发生在容器初始化
  65. 结束以后。第一次
  66. *向容器getBean时,如果设置了lazy-init属性,那么依赖注入发生
  67. 在容器初始化的过程中
  68. ,   *会对
  69. beanDefinitionMap中所有的bean进行依赖注入,这样在初始化过程
  70. 结束以后,
  71. *向容器getBean得到的就是已经准备好的bean,不需要进行依赖注入
  72. 。*/  synchronized
  73. (this.beanDefinitionMap) {   for (String beanName :
  74. this.beanDefinitionNames) {  
  75. RootBeanDefinition bd =
  76. getMergedLocalBeanDefinition(beanName);    if
  77. (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {    
  78. if
  79. (isFactoryBean(beanName)) {       FactoryBean factory =
  80. (FactoryBean)getBean(FACTORY_BEAN_      PREFIX + beanName);     
  81. if (factory
  82. instanceof SmartFactoryBean &&((SmartFactoryBean)    
  83. factory).isEagerInit()) {     
  84. getBean(beanName);       }      }     else {     
  85. getBean(beanName);      }     }
  86. }   }}

根据上面的分析得知,我们可以通过lazy-init属性来对整个IoC容器的初始化和依赖注入过程做一些简单的控制。这些控制是可以由容器的使用者来决定的,具体来说,可以通过在BeanDefinition 中设置 lazy-init 属性来进行控制。这为我们使用容器提供了一定的灵活性,如果了解了这些控制原理,可以帮助我们更好地利用这些特性,希望这些小技巧能够对读者更好地使用容器提供帮助。

2009-10-19 sunlightcs (架构师)

最近在负责一个大项目,项目组成员包括项目经理大概10个人左右。项目技术用struts+spring+hibernate实现。项目的规模相对来说是比较大的,总共有10大模块,每个大模块又分为有十几个、甚至几十个小模块。开发工具用eclipse,由于在开发阶段,项目开发成员需要频繁重启服务器。在启动服务器的时候,每次启动时间总是会超过1分钟。记得以前在做另外一个项目时,启动时间不到5秒钟,相差了10倍,而且项目规模是差不多的。

    从初步分析来说,应该是hibernate解释hbm.xml时花费时间,或者可能是spring容器启动并解释所有的bean配置文件。诊断了一下,发现1分钟消耗的时间主要分布在hibernate解释hbm.xml花费5秒;spring容器从启动到解释bean配置文件竟然花了58秒,真是太嚣张了。当时非常怀疑spring的效率问题。企图从网上搜索相关资料,看看有什么优化措施。

    首先是找到了hibernate的启动优化 http://www.hibernate.org/194.html 里面的主要思想是通过将xml序列花到本地的文件里,每次读取的时候根据情况,从本地文件读取并反序列化,节省了hibernate xml的解析时间。按照这个方式测试了一下,发现hibernate的启动时间从5秒降低到3秒,但是这个优化对于整个启动过程是杯水车薪的,毫无用处。

    没办法,又仔细查看了spring的资料,终于发现spring的容器是提供了lazy-load的,即默认的缺省设置是bean没有lazy- load,该属性处于false状态,这样导致spring在启动过程导致在启动时候,会默认加载整个对象实例图,从初始化ACTION配置、到 service配置到dao配置、乃至到数据库连接、事务等等。这么庞大的规模,难怪spring的启动时间要花将近1分钟。尝试了一下,把beans的 default-lazy-init改为true就,再次启动,速度从原来的55秒,降到8秒钟!!Great!虽然是非常小一个改动,但是影响确实非常大。一个项目组10个人,假若每个人一天平均需要在eclipse下启动测试服务器50次。那么一天项目组需要重启500次,每次节省50秒的话,就是 25000秒,将近几个小时,差不多一个工作日,多么可观的数字!

   不过在运行期间第一次点页面的时候,由于spring做了lazy-load,现在就需要启动一部分需要的beans,所以稍微慢2-3秒钟,但是明显比等几十秒要快很多,值得一鉴。

    以上是针对开发阶段的spring容器启动优化,在部署到实际环境中,倒是没必要设置为lazy-load。毕竟部署到实际环境中不是经常的事,每次启动1分钟倒不是大问题。


Java代码 复制代码
  1.   
  2. 我这里要提醒的是不是说有的beans都能设置default-lazy-init成为true.对于scheduler的bean不能用lazy-init   
  3.   
  4.   
  5.   
  6. < beans  default-lazy-init ="true" >   
  7.        
  8.       < bean  class ="org.springframework.scheduling.quartz.SchedulerFactoryBean" >   
  9.           < property   name ="triggers" >   
  10.               < list >   
  11.                   < ref   bean ="buildHtmlTrigger" />   
  12.                   < ref   bean ="askTrigger" />   
  13.                   < ref   bean ="mailSenderTrigger" />   
  14.                   < ref   bean ="topicDetailBuildTrigger" />   
  15.                   < ref   bean ="forumBuildTrigger" />   
  16.                   < ref   bean ="topicBuildTrigger" />   
  17.               </ list >   
  18.           </ property >   
  19.       </ bean >   
  20. </ beans >   
  21.   
  22.   
  23.   
  24. 这样的话。所有的scheduler就都不管用了。所以请大家要注意。   
  25.   
  26.   
  27. < beans >   
  28.        
  29.       < bean  class ="org.springframework.scheduling.quartz.SchedulerFactoryBean" >   
  30.           < property   name ="triggers" >   
  31.               < list >   
  32.                   < ref   bean ="buildHtmlTrigger" />   
  33.                   < ref   bean ="askTrigger" />   
  34.                   < ref   bean ="mailSenderTrigger" />   
  35.                   < ref   bean ="topicDetailBuildTrigger" />   
  36.                   < ref   bean ="forumBuildTrigger" />   
  37.                   < ref   bean ="topicBuildTrigger" />   
  38.               </ list >   
  39.           </ property >   
  40.       </ bean >   
  41. </ beans >   

类别:Spring||添加到搜藏 |分享到i贴吧|浏览(371)|评论 (0)
 
最近读者:
 
网友评论:
发表评论:
姓 名:
网址或邮箱: (选填)
内 容:
     

   
帮助中心 | 空间客服 | 投诉中心 | 空间协议
©2012 Baidu