`
wangpengfei360
  • 浏览: 1059163 次
文章分类
社区版块
存档分类
最新评论

【第三章】 DI 之 3.4 Bean的作用域 ——跟我学spring3

 
阅读更多

3.4 Bean的作用域

什么是作用域呢?即“scope”,在面向对象程序设计中一般指对象或变量之间的可见范围。而在Spring容器中是指其创建的Bean对象相对于其他Bean对象的请求可见范围。

Spring提供“singleton”和“prototype”两种基本作用域,另外提供“request”、“session”、“global session”三种web作用域;Spring还允许用户定制自己的作用域。

3.4.1 基本的作用域

一、singleton:指“singleton”作用域的Bean只会在每个Spring IoC容器中存在一个实例,而且其完整生命周期完全由Spring容器管理。对于所有获取该Bean的操作Spring容器将只返回同一个Bean。


GoF单例设计模式指“保证一个类仅有一个实例,并提供一个访问它的全局访问点”,介绍了两种实现:通过在类上定义静态属性保持该实例和通过注册表方式。

1)通过在类上定义静态属性保持该实例:一般指一个Java虚拟机 ClassLoader装载的类只有一个实例,一般通过类静态属性保持该实例,这样就造成需要单例的类都需要按照单例设计模式进行编码;Spring没采用这种方式,因为该方式属于侵入式设计;代码样例如下:


java代码:
  1. packagecn.javass.spring.chapter3.bean;
  2. publicclassSingleton{
  3. //1.私有化构造器
  4. privateSingleton(){}
  5. //2.单例缓存者,惰性初始化,第一次使用时初始化
  6. privatestaticclassInstanceHolder{
  7. privatestaticfinalSingletonINSTANCE=newSingleton();
  8. }
  9. //3.提供全局访问点
  10. publicstaticSingletongetInstance(){
  11. returnInstanceHolder.INSTANCE;
  12. }
  13. //4.提供一个计数器来验证一个ClassLoader一个实例
  14. privateintcounter=0;
  15. }



以上定义个了个单例类,首先要私有化类构造器;其次使用InstanceHolder静态内部类持有单例对象,这样可以得到惰性初始化好处;最后提供全局访问点getInstance,使得需要该单例实例的对象能获取到;我们在此还提供了一个counter计数器来验证一个ClassLoader一个实例。具体一个ClassLoader有一个单例实例测试请参考代码“cn.javass.spring.chapter3. SingletonTest”中的“testSingleton”测试方法,里边详细演示了一个ClassLoader有一个单例实例。



1)通过注册表方式:首先将需要单例的实例通过唯一键注册到注册表,然后通过键来获取单例,让我们直接看实现吧,注意本注册表实现了Spring接口“SingletonBeanRegistry”,该接口定义了操作共享的单例对象,Spring容器实现将实现此接口;所以共享单例对象通过“registerSingleton”方法注册,通过“getSingleton”方法获取,消除了编程方式单例,注意在实现中不考虑并发:



java代码:
  1. packagecn.javass.spring.chapter3;
  2. importjava.util.HashMap;
  3. importjava.util.Map;
  4. importorg.springframework.beans.factory.config.SingletonBeanRegistry;
  5. publicclassSingletonBeanRegisterimplementsSingletonBeanRegistry{
  6. //单例Bean缓存池,此处不考虑并发
  7. privatefinalMap<String,Object>BEANS=newHashMap<String,Object>();
  8. publicbooleancontainsSingleton(StringbeanName){
  9. returnBEANS.containsKey(beanName);
  10. }
  11. publicObjectgetSingleton(StringbeanName){
  12. returnBEANS.get(beanName);
  13. }
  14. @Override
  15. publicintgetSingletonCount(){
  16. returnBEANS.size();
  17. }
  18. @Override
  19. publicString[]getSingletonNames(){
  20. returnBEANS.keySet().toArray(newString[0]);
  21. }
  22. @Override
  23. publicvoidregisterSingleton(StringbeanName,Objectbean){
  24. if(BEANS.containsKey(beanName)){
  25. thrownewRuntimeException("["+beanName+"]已存在");
  26. }
  27. BEANS.put(beanName,bean);
  28. }
  29. }


Spring是注册表单例设计模式的实现,消除了编程式单例,而且对代码是非入侵式。

接下来让我们看看在Spring中如何配置单例Bean吧,在Spring容器中如果没指定作用域默认就是“singleton”,配置方式通过scope属性配置,具体配置如下:


java代码:
  1. <beanclass="cn.javass.spring.chapter3.bean.Printer"scope="singleton"/>


Spring管理单例对象在Spring容器中存储如图3-5所示,Spring不仅会缓存单例对象,Bean定义也是会缓存的,对于惰性初始化的对象是在首次使用时根据Bean定义创建并存放于单例缓存池。

图3-5 单例处理


二、prototype:即原型,指每次向Spring容器请求获取Bean都返回一个全新的Bean,相对于“singleton”来说就是不缓存Bean,每次都是一个根据Bean定义创建的全新Bean。


GoF原型设计模式,指用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。


Spring中的原型和GoF中介绍的原型含义是不一样的:

GoF通过用原型实例指定创建对象的种类,而Spring容器用Bean定义指定创建对象的种类;

GoF通过拷贝这些原型创建新的对象,而Spring容器根据Bean定义创建新对象。

其相同地方都是根据某些东西创建新东西,而且GoF原型必须显示实现克隆操作,属于侵入式,而Spring容器只需配置即可,属于非侵入式。


接下来让我们看看Spring如何实现原型呢?


1)首先让我们来定义Bean“原型”:Bean定义,所有对象将根据Bean定义创建;在此我们只是简单示例一下,不会涉及依赖注入等复杂实现:BeanDefinition类定义属性“class”表示原型类,“id”表示唯一标识,“scope”表示作用域,具体如下:


java代码:
  1. packagecn.javass.spring.chapter3;
  2. publicclassBeanDefinition{
  3. //单例
  4. publicstaticfinalintSCOPE_SINGLETON=0;
  5. //原型
  6. publicstaticfinalintSCOPE_PROTOTYPE=1;
  7. //唯一标识
  8. privateStringid;
  9. //class全限定名
  10. privateStringclazz;
  11. //作用域
  12. privateintscope=SCOPE_SINGLETON;
  13. //鉴于篇幅,省略setter和getter方法;
  14. }


2)接下来让我们看看Bean定义注册表,类似于单例注册表:


java代码:
  1. packagecn.javass.spring.chapter3;
  2. importjava.util.HashMap;
  3. importjava.util.Map;
  4. publicclassBeanDifinitionRegister{
  5. //bean定义缓存,此处不考虑并发问题
  6. privatefinalMap<String,BeanDefinition>DEFINITIONS=
  7. newHashMap<String,BeanDefinition>();
  8. publicvoidregisterBeanDefinition(StringbeanName,BeanDefinitionbd){
  9. //1.本实现不允许覆盖Bean定义
  10. if(DEFINITIONS.containsKey(bd.getId())){
  11. thrownewRuntimeException("已存在Bean定义,此实现不允许覆盖");
  12. }
  13. //2.将Bean定义放入Bean定义缓存池
  14. DEFINITIONS.put(bd.getId(),bd);
  15. }
  16. publicBeanDefinitiongetBeanDefinition(StringbeanName){
  17. returnDEFINITIONS.get(beanName);
  18. }
  19. publicbooleancontainsBeanDefinition(StringbeanName){
  20. returnDEFINITIONS.containsKey(beanName);
  21. }
  22. }


3)接下来应该来定义BeanFactory了:


java代码:
  1. packagecn.javass.spring.chapter3;
  2. importorg.springframework.beans.factory.config.SingletonBeanRegistry;
  3. publicclassDefaultBeanFactory{
  4. //Bean定义注册表
  5. privateBeanDifinitionRegisterDEFINITIONS=newBeanDifinitionRegister();
  6. //单例注册表
  7. privatefinalSingletonBeanRegistrySINGLETONS=newSingletonBeanRegister();
  8. publicObjectgetBean(StringbeanName){
  9. //1.验证Bean定义是否存在
  10. if(!DEFINITIONS.containsBeanDefinition(beanName)){
  11. thrownewRuntimeException("不存在["+beanName+"]Bean定义");
  12. }
  13. //2.获取Bean定义
  14. BeanDefinitionbd=DEFINITIONS.getBeanDefinition(beanName);
  15. //3.是否该Bean定义是单例作用域
  16. if(bd.getScope()==BeanDefinition.SCOPE_SINGLETON){
  17. //3.1如果单例注册表包含Bean,则直接返回该Bean
  18. if(SINGLETONS.containsSingleton(beanName)){
  19. returnSINGLETONS.getSingleton(beanName);
  20. }
  21. //3.2单例注册表不包含该Bean,
  22. //则创建并注册到单例注册表,从而缓存
  23. SINGLETONS.registerSingleton(beanName,createBean(bd));
  24. returnSINGLETONS.getSingleton(beanName);
  25. }
  26. //4.如果是原型Bean定义,则直接返回根据Bean定义创建的新Bean,
  27. //每次都是新的,无缓存
  28. if(bd.getScope()==BeanDefinition.SCOPE_PROTOTYPE){
  29. returncreateBean(bd);
  30. }
  31. //5.其他情况错误的Bean定义
  32. thrownewRuntimeException("错误的Bean定义");
  33. }



java代码:
  1. publicvoidregisterBeanDefinition(BeanDefinitionbd){
  2. DEFINITIONS.registerBeanDefinition(bd.getId(),bd);
  3. }
  4. privateObjectcreateBean(BeanDefinitionbd){
  5. //根据Bean定义创建Bean
  6. try{
  7. Classclazz=Class.forName(bd.getClazz());
  8. //通过反射使用无参数构造器创建Bean
  9. returnclazz.getConstructor().newInstance();
  10. }catch(ClassNotFoundExceptione){
  11. thrownewRuntimeException("没有找到Bean["+bd.getId()+"]类");
  12. }catch(Exceptione){
  13. thrownewRuntimeException("创建Bean["+bd.getId()+"]失败");
  14. }
  15. }


其中方法getBean用于获取根据beanName对于的Bean定义创建的对象,有单例和原型两类Bean;registerBeanDefinition方法用于注册Bean定义,私有方法createBean用于根据Bean定义中的类型信息创建Bean。


3)测试一下吧,在此我们只测试原型作用域Bean,对于每次从Bean工厂中获取的Bean都是一个全新的对象,代码片段(BeanFatoryTest)如下:


java代码:
  1. @Test
  2. publicvoidtestPrototype()throwsException{
  3. //1.创建Bean工厂
  4. DefaultBeanFactorybf=newDefaultBeanFactory();
  5. //2.创建原型Bean定义
  6. BeanDefinitionbd=newBeanDefinition();
  7. bd.setId("bean");
  8. bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
  9. bd.setClazz(HelloImpl2.class.getName());
  10. bf.registerBeanDefinition(bd);
  11. //对于原型Bean每次应该返回一个全新的Bean
  12. System.out.println(bf.getBean("bean")!=bf.getBean("bean"));
  13. }



最后让我们看看如何在Spring中进行配置吧,只需指定<bean>标签属性“scope”属性为“prototype”即可:


java代码:
  1. <beanclass="cn.javass.spring.chapter3.bean.Printer"/>



Spring管理原型对象在Spring容器中存储如图3-6所示,Spring不会缓存原型对象,而是根据Bean定义每次请求返回一个全新的Bean:

图3-6 原型处理

单例和原型作用域我们已经讲完,接下来让我们学习一些在Web应用中有哪些作用域:

3.4.2 Web应用中的作用域

在Web应用中,我们可能需要将数据存储到request、session、globalsession。因此Spring提供了三种Web作用域:request、session、globalSession。


一、request作用域:表示每个请求需要容器创建一个全新Bean。比如提交表单的数据必须是对每次请求新建一个Bean来保持这些表单数据,请求结束释放这些数据。


二、session作用域:表示每个会话需要容器创建一个全新Bean。比如对于每个用户一般会有一个会话,该用户的用户信息需要存储到会话中,此时可以将该Bean配置为web作用域。


三、globalSession:类似于session作用域,只是其用于portlet环境的web应用。如果在非portlet环境将视为session作用域。


配置方式和基本的作用域相同,只是必须要有web环境支持,并配置相应的容器监听器或拦截器从而能应用这些作用域,我们会在集成web时讲解具体使用,大家只需要知道有这些作用域就可以了。

3.4.4自定义作用域

在日常程序开发中,几乎用不到自定义作用域,除非又必要才进行自定义作用域。

首先让我们看下Scope接口吧:


java代码:
  1. packageorg.springframework.beans.factory.config;
  2. importorg.springframework.beans.factory.ObjectFactory;
  3. publicinterfaceScope{
  4. Objectget(Stringname,ObjectFactory<?>objectFactory);
  5. Objectremove(Stringname);
  6. voidregisterDestructionCallback(Stringname,Runnablecallback);
  7. ObjectresolveContextualObject(Stringkey);
  8. StringgetConversationId();
  9. }


1)Object get(String name, ObjectFactory<?> objectFactory):用于从作用域中获取Bean,其中参数objectFactory是当在当前作用域没找到合适Bean时使用它创建一个新的Bean;


2)void registerDestructionCallback(String name, Runnable callback):用于注册销毁回调,如果想要销毁相应的对象则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象;


3)Object resolveContextualObject(String key):用于解析相应的上下文数据,比如request作用域将返回request中的属性。


4)String getConversationId():作用域的会话标识,比如session作用域将是sessionId。



java代码:
  1. packagecn.javass.spring.chapter3;
  2. importjava.util.HashMap;
  3. importjava.util.Map;
  4. importorg.springframework.beans.factory.ObjectFactory;
  5. importorg.springframework.beans.factory.config.Scope;
  6. publicclassThreadScopeimplementsScope{
  7. privatefinalThreadLocal<Map<String,Object>>THREAD_SCOPE=
  8. newThreadLocal<Map<String,Object>>(){
  9. protectedMap<String,Object>initialValue(){
  10. //用于存放线程相关Bean
  11. returnnewHashMap<String,Object>();
  12. }
  13. };


让我们来实现个简单的thread作用域,该作用域内创建的对象将绑定到ThreadLocal内。


java代码:
  1. @Override
  2. publicObjectget(Stringname,ObjectFactory<?>objectFactory){
  3. //如果当前线程已经绑定了相应Bean,直接返回
  4. if(THREAD_SCOPE.get().containsKey(name)){
  5. returnTHREAD_SCOPE.get().get(name);
  6. }
  7. //使用objectFactory创建Bean并绑定到当前线程上
  8. THREAD_SCOPE.get().put(name,objectFactory.getObject());
  9. returnTHREAD_SCOPE.get().get(name);
  10. }
  11. @Override
  12. publicStringgetConversationId(){
  13. returnnull;
  14. }
  15. @Override
  16. publicvoidregisterDestructionCallback(Stringname,Runnablecallback){
  17. //此处不实现就代表类似proytotype,容器返回给用户后就不管了
  18. }
  19. @Override
  20. publicObjectremove(Stringname){
  21. returnTHREAD_SCOPE.get().remove(name);
  22. }
  23. @Override
  24. publicObjectresolveContextualObject(Stringkey){
  25. returnnull;
  26. }
  27. }


Scope已经实现了,让我们将其注册到Spring容器,使其发挥作用:


java代码:
  1. <beanclass="org.springframework.beans.factory.config.CustomScopeConfigurer">
  2. <propertyname="scopes">
  3. <map><entry>
  4. <!--指定scope关键字--><key><value>thread</value></key>
  5. <!--scope实现--><beanclass="cn.javass.spring.chapter3.ThreadScope"/>
  6. </entry></map>
  7. </property>
  8. </bean>


通过CustomScopeConfigurer的scopes属性注册自定义作用域实现,在此需要指定使用作用域的关键字“thread”,并指定自定义作用域实现。来让我们来定义一个“thread”作用域的Bean,配置(chapter3/threadScope.xml)如下:


java代码:
  1. <beanid="helloApi"
  2. class="cn.javass.spring.chapter2.helloworld.HelloImpl"
  3. scope="thread"/>


最后测试(cn.javass.spring.chapter3.ThreadScopeTest)一下吧,首先在一个线程中测试,在同一线程中获取的Bean应该是一样的;再让我们开启两个线程,然后应该这两个线程创建的Bean是不一样:



自定义作用域实现其实是非常简单的,其实复杂的是如果需要销毁Bean,自定义作用域如何正确的销毁Bean。


原创内容 转载请注明出处【http://sishuok.com/forum/blogPost/list/2454.html


分享到:
评论

相关推荐

    跟我学spring3(1-7)

    【第三章】 DI 之 3.4 Bean的作用域 ——跟我学spring3 【第四章】 资源 之 4.1 基础知识 ——跟我学spring3 【第四章】 资源 之 4.2 内置Resource实现 ——跟我学spring3 【第四章】 资源 之 4.3 访问Resource ——...

    跟开涛学Spring

    1.9 【第三章】 DI 之 3.4 Bean的作用域 ——跟我学spring3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .121 1.10 »Spring 之AOP AspectJ切入点语法详解(最全了,不需要再去其他地找了) . ...

    跟我学spring3(1-7).pdf

    Spring概述2.1 IoC基础2.2 IoC 容器基本原理2.3 IoC的配置使用——跟我学Spring33.1 DI的配置使用3.2 循环依赖3.3 更多DI的知识 3.4 Bean的作用域 4.1 基础知识4.2 内置Resource实现4.3 访问Resource4.4 Resource...

    跟我学Spring系列1

    【第二章】 IoC 之 2.2 IoC 容器基本原理 ——跟我学【第二章】 IoC 之 2.3 IoC的配置使用——跟我学Spring3【第三章】 DI 之 3

    Spring从入门到入土——Bean的作用域

    Bean的作用域Bean的作用...Spring从入门到入土——Bean的作用域 Bean的作用域 ​ 在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的

    跟我学spring3 .pdf

    详细讲解了 IOC DI AOP JDBC MVC 等等spring知识,有很高的学习价值

    跟我学Spring3(3.3)更多的DI知识Java开发J

    跟我学Spring3(3.3)更多的DI知识Java开发Java经验技巧共20页.pdf.zip

    Spring从入门到入土——依赖注入(DI)

    DIDependency Injection概念注入方式构造器注入**==Set注入==**测试pojo类:...Spring从入门到入土——Bean的作用域 Dependency Injection 概念 依赖注入(DI) 依赖:指Bean对象的创建依赖于容器。Bean对象的依赖资

    Spring的Bean配置

    Spring IOC和DI概述,Bean的配置形式,IOC容器BeanFactory和ApplicationContext概述,依赖注入的方式,属性注入,构造器注入等案例

    跟我学spring3(8-13).pdf

    spring3基础知识 IoC DI Spring表达式 SpEL Spring JDBC支持 Spring ORM集成 Spring与其他web框架集成 Spring注解零配置 Spring单元测试与集成测试

    spring杂谈 作者zhang KaiTao

    1. spring杂谈[原创] 1.1 Spring事务处理时自我调用的解决方案及一些实现方式的风险 ...1.32 Spring3 Web MVC下的数据类型转换(第一篇)——《跟我学Spring3 Web MVC》抢先看 1.33 Spring 注入集合类型

    spring培训-笔记

    重构第三步——工厂(Factory)模式的改进 10 重构第四步-IoC容器 11 控制反转(IoC)/依赖注入(DI) 11 什么是控制反转/依赖注入? 11 依赖注入的三种实现形式 12 BeanFactory 14 BeanFactory管理Bean...

    Spring教程  主要内容:介绍Spring的历史,Spring的概论和它的体系结构,重点阐述它在J2EE中扮演的角色。

    重构第三步——工厂(Factory)模式的改进 10 重构第四步-IoC容器 11 控制反转(IoC)/依赖注入(DI) 11 什么是控制反转/依赖注入? 11 依赖注入的三种实现形式 12 BeanFactory 14 BeanFactory管理Bean(组件)的...

    JAVA spring 系列案例50个和学习资料

    Spring系列第3篇:Spring容器基本使用及原理。Spring系列第4篇:xml中bean定义详解(-)Spring系列第5篇:创建bean实例这些方式你们都知道?Spring系列第6篇:玩转bean scope,避免跳坑里!Spring系列第7篇:依赖注入...

    尚硅谷佟刚Spring4代码及PPT.rar

    代码及ppt涵盖 Spring4.0 的所有核心内容:在 Eclipse 中安装 SpringIDE 插件、IOC & DI、在 Spring 中配置 Bean、自动装配、Bean 之间的关系(依赖、继承)、Bean 的作用域、使用外部属性文件、SpEL、管理 Bean 的...

    spring.doc

    3.4 Bean的作用域 16 Scope单例多例作用域拓展: 16 3.4.1 singleton(默认值) 16 3.4.2 prototype 17 3.4.3 Request 17 3.4.4 Session 18 3.4.5 Global session 18 3.4.6 指定Bean的初始化方法和销毁方法 18 Bean的...

    Spring in Action 中文版 第2版 第二部分

    第3章 高级bean装配 第4章 通知bean 第二部分 企业spring 第5章 使用数据库 第6章 事务管理 第7章 保护spring 第8章 spring和基于pojo的远程服务 第9章 在spring中建立契约优先web服务 第10章 spring消息 第...

    Spring in Action 中文版 第2版 第一部分

    第3章 高级bean装配 第4章 通知bean 第二部分 企业spring 第5章 使用数据库 第6章 事务管理 第7章 保护spring 第8章 spring和基于pojo的远程服务 第9章 在spring中建立契约优先web服务 第10章 spring消息 第...

    SPRING DI注入例子

    SPRING DI注入例子 jar包没有包含

    SpringDI.rar

    Spring依赖注入(DI)的例子,包括接口注入、构造注入、set注入的简单类型和复杂类型注入的例子。

Global site tag (gtag.js) - Google Analytics