Bean的作用域

在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为Bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象,除此之外,bean就与应用程序中的其他对象没有什么区别了。而bean的定义以及bean相互间的依赖关系将通过配置元数据来描述。

Bean的五种作用域

在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为Bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象,除此之外,bean就与应用程序中的其他对象没有什么区别了。而bean的定义以及bean相互间的依赖关系将通过配置元数据来描述。

下面就是Spring直接支持的五种作用域了,当然开发者也可以自己定制作用域。

作用域 作用域说明
singleton 容器中仅存在一个对象,默认值
prototype 每调用一次getBean(),都返回一个新的对象
request 每一个HTTP请求会产生一个Bean对象
session 同一个Http Session共用一个Bean
global session 类似于seesion作用域,仅在portletweb应用中有意义

说明:request,session以及global session这三个作用域都是只有在基于web的SpringApplicationContext实现的(比如XmlWebApplicationContext)中才能使用。 如果开发者仅仅在常规的Spring IoC容器中比如ClassPathXmlApplicationContext中使用这些作用域,那么将会抛出一个IllegalStateException来说明使用了未知的作用域。

singleton

当定义一个Bean的作用域为singleton时,容器只会根据Bean定义来创建该Bean的唯一实例。这些唯一的实例会缓存到容器中,后续针对单例Bean的请求和引用,都会从这个缓存中拿到这个唯一的实例。

Singleton作用域是Spring中的缺省作用域。不需要在类上面加任何注解,如:

package com.morris.spring.entity.scope;public class SingletonBean {}

然后使用AnnotationConfigApplicationContext将SingletonBean注入到spring容器中:

package com.morris.spring.demo.annotation;import com.morris.spring.entity.scope.SingletonBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class SingletonBeanDemo {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SingletonBean.class);System.out.println(applicationContext.getBean(SingletonBean.class));System.out.println(applicationContext.getBean(SingletonBean.class));}
}

运行结果如下:

com.morris.spring.entity.scope.SingletonBean@1af5db5
com.morris.spring.entity.scope.SingletonBean@1af5db5

可以发现两次的打印结果一致,说明获取的SingletonBean是同一个实例。

prototype

prototype指的就是每次请求Bean实例的时候,返回的都是新实例的Bean对象。这是基于线程安全性的考虑,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。

下面的例子展示了如何定义一个原型的Bean:

package com.morris.spring.entity.scope;import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeBean {}

然后使用AnnotationConfigApplicationContext将SingletonBean注入到spring容器中:

package com.morris.spring.demo.annotation;import com.morris.spring.entity.scope.PrototypeBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class PrototypeBeanDemo {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(PrototypeBean.class);System.out.println(applicationContext.getBean(PrototypeBean.class));System.out.println(applicationContext.getBean(PrototypeBean.class));}
}

运行结果如下:

com.morris.spring.entity.scope.PrototypeBean@b0ed20
com.morris.spring.entity.scope.PrototypeBean@e24743

可以发现两次的打印结果不一致,说明获取的PrototypeBean不是同一个实例。

对于Prototype作用域的Bean,不管是同一个线程获取多次,还是不同的线程获取多次得到的都是一个新的实例。

与其他的作用域相比,Spring是不会完全管理原型Bean的生命周期的:Spring容器只会初始化,配置以及装载这些Bean,传递给Client。但是之后就不会再去管原型Bean之后的动作了。

也就是说,初始化生命周期回调方法在所有作用域的Bean是都会调用的,但是销毁生命周期回调方法在原型Bean是不会调用的。所以,客户端代码必须注意清理原型Bean以及释放原型Bean所持有的一些资源。可以通过使用自定义的BeanPostProcessor来让Spring释放掉原型Bean所持有的资源。

自定义scope

自定义scope需要失效Scope接口。

package com.morris.spring.entity.scope;import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;import java.util.Objects;public class ThreadScope implements Scope {private ThreadLocal<Object> threadLocal = new ThreadLocal<>();@Overridepublic Object get(String name, ObjectFactory<?> objectFactory) {Object o = threadLocal.get();if(Objects.isNull(o)) {o = objectFactory.getObject();threadLocal.set(o);}return o;}@Overridepublic Object remove(String name) {Object o = threadLocal.get();threadLocal.remove();return o;}@Overridepublic void registerDestructionCallback(String name, Runnable callback) {}@Overridepublic Object resolveContextualObject(String key) {return null;}@Overridepublic String getConversationId() {return Thread.currentThread().getName();}
}

自定义Score的使用:

package com.morris.spring.entity.scope;import org.springframework.context.annotation.Scope;@Scope("threadScope")
public class ThreadScopeBean {}

然后使用AnnotationConfigApplicationContext将ThreadScopeBean注入到spring容器中:

package com.morris.spring.demo.annotation;import com.morris.spring.entity.scope.ThreadScope;
import com.morris.spring.entity.scope.ThreadScopeBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class ThreadScopeDemo {public static void main(String[] args) throws InterruptedException {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();applicationContext.getBeanFactory().registerScope("threadScope", new ThreadScope());applicationContext.register(ThreadScopeBean.class);applicationContext.refresh();System.out.println(applicationContext.getBean(ThreadScopeBean.class));System.out.println(applicationContext.getBean(ThreadScopeBean.class));Thread thread = new Thread(() -> System.out.println(applicationContext.getBean(ThreadScopeBean.class)));thread.start();thread.join();}
}

运行结果如下:

com.morris.spring.entity.scope.ThreadScopeBean@1fe1d71
com.morris.spring.entity.scope.ThreadScopeBean@1fe1d71
com.morris.spring.entity.scope.ThreadScopeBean@5fb7c4

从运行结果可以发现,同一个线程获取的实例是同一个,不同的线程获取的实例不同。

源码分析

prototype作用域bean的创建

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

else if (mbd.isPrototype()) {// It's a prototype -> create a new instance.// prototype类型的bean的实例化Object prototypeInstance = null;try {// 将beanName加入到当前创建Bean池中beforePrototypeCreation(beanName);// 每次进来都创建一个新的beanprototypeInstance = createBean(beanName, mbd, args);}finally {// 将beanName从当前创建Bean池中移除afterPrototypeCreation(beanName);}bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

自定义作用域bean的创建

将自定义的Scope注册到BeanFactory中:
org.springframework.beans.factory.support.AbstractBeanFactory#registerScope

public void registerScope(String scopeName, Scope scope) {Assert.notNull(scopeName, "Scope identifier must not be null");Assert.notNull(scope, "Scope must not be null");if (SCOPE_SINGLETON.equals(scopeName) || SCOPE_PROTOTYPE.equals(scopeName)) {throw new IllegalArgumentException("Cannot replace existing scopes 'singleton' and 'prototype'");}Scope previous = this.scopes.put(scopeName, scope);if (previous != null && previous != scope) {if (logger.isDebugEnabled()) {logger.debug("Replacing scope '" + scopeName + "' from [" + previous + "] to [" + scope + "]");}}else {if (logger.isTraceEnabled()) {logger.trace("Registering scope '" + scopeName + "' with implementation [" + scope + "]");}}
}

自定义作用域bean的创建过程:
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

else {// 自定义作用域String scopeName = mbd.getScope();if (!StringUtils.hasLength(scopeName)) {throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");}// 根据@Scope注解中的值获取自定义ScopeScope scope = this.scopes.get(scopeName);if (scope == null) {throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");}try {// 调用scope的get方法获得实例// get方法中会传入一个ObjectFactory的lambda表达式,// 在get方法里面可以自行决定是否需要调用ObjectFactory.getObject()来创建新的对象Object scopedInstance = scope.get(beanName, () -> {beforePrototypeCreation(beanName);try {// 创建一个新的beanreturn createBean(beanName, mbd, args);}finally {afterPrototypeCreation(beanName);}});bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);}catch (IllegalStateException ex) {throw new BeanCreationException(beanName,"Scope '" + scopeName + "' is not active for the current thread; consider " +"defining a scoped proxy for this bean if you intend to refer to it from a singleton",ex);}
}

【spring】Bean的Scope与自定义Scope相关推荐

  1. Spring(10)——bean作用范围(二)—自定义scope

    10.7 自定义Scope 如果用户觉得Spring内置的几种Scope不能满足需求,则可以定制自己的Scope,即实现自己的org.springframework.beans.factory.con ...

  2. 【Spring注解驱动开发】使用@Scope注解设置组件的作用域

    写在前面 Spring容器中的组件默认是单例的,在Spring启动时就会实例化并初始化这些对象,将其放到Spring容器中,之后,每次获取对象时,直接从Spring容器中获取,而不再创建对象.如果每次 ...

  3. java spring scope_如何在Spring中自定义scope的方法示例

    大家对于 Spring 的 scope 应该都不会默认.所谓 scope,字面理解就是"作用域"."范围",如果一个 bean 的 scope 配置为 sing ...

  4. spring bean属性scope

    ‍‍<bean id="role" class="spring.chapter2.maryGame.Role" scope="singleton ...

  5. error:lnk2005 已经在*.obj中定义_如何在 Spring 中自定义 scope

    大家对于 Spring 的 scope 应该都不会默认.所谓 scope,字面理解就是"作用域"."范围",如果一个 bean 的 scope 配置为 sing ...

  6. 【Spring】Spring 自定义scope

    1.概述 有时候,spring内置的几种sope都无法满足我们的需求的时候,我们可以自定义bean的作用域. 1.1 自定义Scope 3步骤 1.1.1 第1步:实现Scope接口 我们来看一下这个 ...

  7. 如何在 Spring 中自定义 scope

    大家对于 Spring 的 scope 应该都不会默认.所谓 scope,字面理解就是"作用域"."范围",如果一个 bean 的 scope 配置为 sing ...

  8. Spring bean - scope详解

    ​ Scope是定义Spring如何创建bean的实例的. 在创建bean的时候可以带上scope属性,scope有下面几种类型. Singleton 这也是Spring默认的scope,表示Spri ...

  9. Spring Bean 的scope什么时候设置为prototype,什么时候设置为singleton

    Java代码   <bean id = "testAction" class = "com.kewen.xxxAction" scope = " ...

最新文章

  1. Python基础(10)--数字
  2. 回顾inputstream和outputstream
  3. 美国科技三巨头的财报为何集体爆表?原因在这里
  4. mysql架设_服务器架设MySQL开发规范与使用技巧
  5. HH SaaS电商系统的仓储系统设计
  6. 乒乓球十一分制比赛规则_乒乓球的基本比赛规则
  7. mjorm java_MongoDB 的 ORM框架 MJORM
  8. escilpse 连接mysql,浅谈docker-compose网络设置之networks
  9. sql 日期和当前日期时间差_SQL基础进阶16日期处理
  10. 摘录:java和sql如何判断数据库是否存在
  11. hdu1403(后缀数组模板)
  12. 灰色系统理论与灰色关联分析模型
  13. 地面波天线怎样能多收台_教你怎样挑选DTMB地面波天线
  14. 一、爬虫 - 新浪爱问共享资源全下载之解决方案
  15. 大白话讲清楚JVM里的方法区、永久代以及元空间
  16. Ubuntu10.10下安装Tor,PolipoVidalia
  17. 【转载】网易博客完美支持Word写日志
  18. 骗的就是你!揭露买本10大愚蠢表现
  19. mysql 冗余 raid_RAID(廉价磁盘冗余阵列)
  20. 【数据结构与算法】删除线性表中的零元素

热门文章

  1. 协方差的计算公式例子_协方差的意义和计算公式
  2. python的api库_python 库api
  3. linux ssh连接交换机_如何配置交换机SSH远程登录
  4. 下一代互联网:“一切变得更快更简单”
  5. Java中String的indexof()的用法
  6. 0713 重装anaconda + pytorch+jupyter+d2l系统
  7. python-17-并行计算和分布式计算框架dask
  8. 实战Quartz的Scheduler
  9. 多段曲线控温从Simulink仿真到PLC控制实现
  10. RedisAssistant:一款Redis可视化管理工具