Scope及源码剖析-玩转Spring各种作用域Bean
环境:Spring5.3.23
一.简介
SpringScopeBean是Spring用于治理Bean的作用域的一种机制。它定义了容器中Bean的生命周期和实例化战略,即如何创立Bean实例。
在Spring中,Bean的作用域包含单例(singleton)、原型(prototype)、恳求(request)、会话(session)等。每个作用域都有其特定的经常使用场景和行为:
此外,Spring还提供了其余一些作用域运行(lication)、WebSocket,以满足不同场景的需求。
经过正当地选用Bean的作用域,可以优化运行的性能和资源应用率。例如,关于须要频繁创立和销毁实例的Bean,经常使用原型作用域会更高效;而关于须要在多个恳求或会话之间共享形态的Bean,则可以选用单例或会话作用域。附官网图:
图片
接上去将区分引见每一种作用域bean。
二.作用域运行
基础类
staticclassPerson{@OverridepublicStringtoString(){returnsuper.toString()+"-"+this.hashCode()+"";}}
2.1单例(singleton)
自动经常使用@Bean,@Service,@Controller注解标注的注解都是单例的。也可以同@Scope注解指定作用域为单例
@Bean//不指定@Scope自动就是单例@Scope(value="singleton")publicPersonperson(){returnnewPerson();}
测试
try(AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext()){context.registerBean(Config.class);context.refresh();System.out.println(context.getBean(Person.class));System.out.println(context.getBean(Person.class));}
控制台输入
com.pack.mn.scope.ScopeMain5$Person@5e0e82ae-1578009262com.pack.main.scope.ScopeMain5$Person@5e0e82ae-1578009262
每次失掉的都是同一个实例。
原理
publicabstractclassAbstractBeanFactory{protected<T>TdoGetBean(...){//...//判别能否是单例if(mbd.isSingleton()){//先从单例池中查找能否曾经存在,不存在则调用createBean创立,//而后存入单例池中sharedInstance=getSingleton(beanName,()->{try{returncreateBean(beanName,mbd,args);}});}//...}}
2.2原型(prototype)
每次冷静器中恳求Bean时,都会创立一个新的Bean实例。
@Bean@Scope(value="prototype")publicPersonperson(){returnnewPerson();}
控制台输入
com.pack.main.scope.ScopeMain5$Person@fa4c865-262457445com.pack.main.scope.ScopeMain5$Person@3bd82cf5-1004023029
每次失掉都是不同的对象。
原理
publicabstractclassAbstractBeanFactory{protected<T>TdoGetBean(...){//...//判别能否是单例if(mbd.isSingleton()){//...}//判别能否是原型elseif(mbd.isPrototype()){ObjectprototypeInstance=null;try{//不存在什么缓存池,间接创立bean实例前往prototypeInstance=createBean(beanName,mbd,args);}}//...}}
这里思考一个疑问,如何在单例bean中正确的注入原型bean?
2.3恳求(request)
接上去都是与web环境关系了,所以这里展示的示例会以SpringBoot3.0.5环境展示。
基础类
@Component@Scope(value="request")publicclassPerson{}
测试类
@RestController@RequestMapping("/scopes")publicclassScopeController{@ResourceprivatePersonperson;@ResourceprivatePersonServiceps;@GetMapping("/request")publicPersonrequest(){System.out.println("ScopeController:"+person);ps.query();returnperson;}}
@ServicepublicclassPersonService{@ResourceprivatePersonperson;publicvoidquery(){System.out.println("PersonService:"+person);}}
假设上方这样性能,启动服务将会报错:
Causedby:.lang.IllegalStateException:Nothread-boundrequestfound:Areyoureferringtorequestattributesoutsideofanactualwebrequest,orprocessingarequestoutsideoftheoriginallyreceivingthread?Ifyouareactuallyoperatingwithinawebrequestandstillreceivethismessage,yourcodeisprobablyrunningoutsideofDispatcherServlet:Inthiscase,useRequestContextListenerorRequestContextFiltertoexposethecurrentrequest.atorg.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)~[spring-web-6.0.7.jar:6.0.7]
该失误的要素就是你在一个单例bean中注入一个request作用域的bean,而request作用域bean的生命周期是在一个web恳求开局创立的,所以这里你当然是没法注入的。
处置方法:
@Component@Scope(value="request",proxyMode=ScopedProxyMode.TARGET_CLASS)publicclassPerson{}
测试结果
ScopeController:com.pack.scopes.Person@106a9684-275420804PersonService:com.pack.scopes.Person@106a9684-275420804ScopeController:com.pack.scopes.Person@64396678-1681483384PersonService:com.pack.scopes.Person@64396678-1681483384
每次恳求接口都失掉的不是同一个实例。并且在一个完整的恳求中失掉的Person都是同一个。
该注解原理与上方其实分歧的
@Scope(WebApplicationContext.SCOPE_REQUEST)public@interfaceRequestScope{@AliasFor(annotation=Scope.class)//设置好了经常使用代理ScopedProxyModeproxyMode()defaultScopedProxyMode.TARGET_CLASS;}
2.4会话(session)
@Component@Scope(value="session",proxyMode=ScopedProxyMode.TARGET_CLASS)//与request一样,肯定设置代理形式或许经常使用上方这个注解//@SessionScopepublicclassPerson{}
测试
ScopeController:com.pack.scopes.Person@2b56038d-727057293PersonService:com.pack.scopes.Person@2b56038d-727057293ScopeController:com.pack.scopes.Person@2b56038d-727057293PersonService:com.pack.scopes.Person@2b56038d-727057293
屡次访问都是同一个session;你再换个阅读器访问
ScopeController:com.pack.scopes.Person@1aa201fd-446824957PersonService:com.pack.scopes.Person@1aa201fd-446824957ScopeController:com.pack.scopes.Person@1aa201fd-446824957PersonService:com.pack.scopes.Person@1aa201fd-446824957
此时对象就是一个新的了,不同的阅读器访问当然不是同一个session了。
2.5运行(application)
@Scope(value="application",proxyMode=ScopedProxyMode.TARGET_CLASS)//@ApplicationScope//都是web环境,所以状况都一样publicclassPerson{}
测试
360阅读器
ScopeController:com.pack.scopes.Person@6371b4b6-1668396214PersonService:com.pack.scopes.Person@6371b4b6-1668396214
Chrome阅读器
ScopeController:com.pack.scopes.Person@6371b4b6-1668396214PersonService:com.pack.scopes.Person@6371b4b6-1668396214
他们是同一个对象,application作用域生命周期与整个运行一样,只要你封锁了主机,在启动后才会是再从新创立的bean对象。
3.web作用域原理
3.1注册作用域
publicabstractclassAbstractApplicationContext{publicvoidrefresh(){postProcessBeanFactory(beanFactory);}}publicclassAnnotationConfigServletWebServerApplicationContext{protectedvoidpostProcessBeanFactory(ConfigurableListableBeanFactorybeanFactory){super.postProcessBeanFactory(beanFactory);}}publicclassServletWebServerApplicationContext{protectedvoidpostProcessBeanFactory(ConfigurableListableBeanFactorybeanFactory){//...registerWebApplicationScopes();}privatevoidregisterWebApplicationScopes(){WebApplicationContextUtils.registerWebApplicationScopes(getBeanFactory());}}publicabstractclassWebApplicationContextUtils{publicstaticvoidregisterWebApplicationScopes(ConfigurableListableBeanFactorybeanFactory){registerWebApplicationScopes(beanFactory,null);}publicstaticvoidregisterWebApplicationScopes(ConfigurableListableBeanFactorybeanFactory,@NullableServletContextsc){//注册作用域beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST,newRequestScope());beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION,newSessionScope());if(sc!=null){ServletContextScopeappScope=newServletContextScope(sc);beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION,appScope);}}}
这里每一种web作用域都有一个对应的Scope成功RequestScope,SessionScope,ServletContextScope。
3.2查找web作用域bean
publicabstractclassAbstractBeanFactory{protected<T>TdoGetBean(...){//...//判别能否是单例if(mbd.isSingleton()){//...}//判别能否是原型elseif(mbd.isPrototype()){ObjectprototypeInstance=null;try{//不存在什么缓存池,间接创立bean实例前往prototypeInstance=createBean(beanName,mbd,args);}}//其它作用域bean,如上方的web作用域else{StringscopeName=mbd.getScope();Scopescope=this.scopes.get(scopeName);if(scope==null){thrownewIllegalStateException("NoScoperegisteredforscopename'"+scopeName+"'");}try{//经过详细Scope的成功类失掉bean对象ObjectscopedInstance=scope.get(beanName,()->{beforePrototypeCreation(beanName);try{//初次都还是会创立returncreateBean(beanName,mbd,args);}});}}}//...}}
总结:SpringScopeBean是Spring框架中用于治理Bean的作用域的机制,它定义了Bean的生命周期和实例化战略。经过正当地选用Bean的作用域,可以优化运行的性能和资源应用率。
详解Spring中bean的scope
如何使用spring的作用域
这里的scope就是用来配置spring bean的作用域 它标识bean的作用域 在spring 之前bean只有 种作用域即 singleton(单例) non singleton(也称prototype) Spring 以后 增加了session request global session三种专用于Web应用程序上下文的Bean 因此 默认情况下Spring 现在有五种类型的Bean 当然 Spring 对Bean的类型的设计进行了重构 并设计出灵活的Bean类型支持 理论上可以有无数多种类型的Bean 用户可以根据自己的需要 增加新的Bean类型 满足实际应用需求
singleton作用域
当一个bean的作用域设置为singleton 那么Spring IOC容器中只会存在一个共享的bean实例 并且所有对bean的请求 只要id与该bean定义相匹配 则只会返回bean的同一实例 换言之 当把一个bean定义设置为singleton作用域时 Spring IOC容器只会创建该bean定义的唯一实例 这个单一实例会被存储到单例缓存(singleton cache)中 并且所有针对该bean的后续请求和引用都将返回被缓存的对象实例 这里要注意的是singleton作用域和GOF设计模式中的单例是完全不同的 单例设计模式表示一个ClassLoader中只有一个class存在 而这里的singleton则表示一个容器对应一个bean 也就是说当一个bean被标识为singleton时候 spring的IOC容器中只会存在一个该bean
配置实例
或者
prototype作用域部署的bean 每一次请求(将其注入到另一个bean中 或者以程序的方式调用容器的getBean()方法)都会产生一个新的bean实例 相当与一个new的操作 对于prototype作用域的bean 有一点非常重要 那就是Spring不能对一个prototype bean的整个生命周期负责 容器在初始化 配置 装饰或者是装配完一个prototype实例后 将它交给客户端 随后就对该prototype实例不闻不问了 不管何种作用域 容器都会调用所有对象的初始化生命周期回调方法 而对prototype而言 任何配置好的析构生命周期回调方法都将不会被调用 清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源 都是客户端代码的职责 (让Spring容器释放被singleton作用域bean占用资源的一种可行方式是 通过使用bean的后置处理器 该处理器持有要被清除的bean的引用 )
配置实例
或者
request表示该针对每一次HTTP请求都会产生一个新的bean 同时该bean仅在当前HTTP request内有效 配置实例 request session global session使用的时候首先要在初始化web的web xml中做如下配置 如果你使用的是Servlet 及以上的web容器 那么你仅需要在web应用的XML声明文件web xml中增加下述ContextListener即可
<listener class> sprntext request RequestContextListener</listener class>
如果是Servlet 以前的web容器 那么你要使用一个javax servlet Filter的实现
<filter name>requestContextFilter</filter name>
<filter class> springframework web filter RequestContextFilter</filter class>
<filter name>requestContextFilter</filter name>
接着既可以配置bean的作用域了
session作用域表示该针对每一次HTTP请求都会产生一个新的bean 同时该bean仅在当前HTTP session内有效 配置实例
配置实例 和request配置实例的前提一样 配置好web启动文件就可以如下配置
global session
global session作用域类似于标准的HTTP Session作用域 不过它仅仅在基于portlet的web应用中才有意义 Portlet规范定义了全局Session的概念 它被所有构成某个portlet web应用的各种不同的portlet所共享 在global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内 如果你在web中使用global session作用域来标识bean 那么web会自动当成session类型来使用 配置实例 和request配置实例的前提一样 配置好web启动文件就可以如下配置
自定义bean装配作用域在spring 中作用域是可以任意扩展的 你可以自定义作用域 甚至你也可以重新定义已有的作用域(但是你不能覆盖singleton和prototype) spring的作用域由接口 springframewonfig Scope来定义 自定义自己的作用域只要实现该接口即可 下面给个实例 我们建立一个线程的scope 该scope在表示一个线程中有效 代码如下
publicclass MyScope implements Scope {
privatefinal ThreadLocal threadScope = new ThreadLocal() {
protected Object initialValue() {
returnnew HashMap();
public Object get(String name ObjectFactory objectFactory) {
Map scope = (Map) threadScope get();
Object object = scope get(name);
if(object==null) {
object = objectFactory getObject();
scope put(name object);
return object;
public Object remove(String name) {
Map scope = (Map) threadScope get();
return scope remove(name);
publicvoid registerDestructionCallback(String name Runnable callback) {
public String getConversationId() {
// TODO Auto generated method stub
returnnull;
lishixinzhi/Article/program/Java/hx//spring bean scope作用域及多线程安全问题场景分析
在 Spring IoC 容器中具有以下几种作用域: @scope默认是单例模式(singleton),如果需要设置的话@scope(prototype) 或xml配置如下: bean一旦实例化就被加进会话池中,各个用户都可以共用。 即使用户已经消亡,bean 的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。 由于没有特定的用户,那么也就不能保持某一用户的状态,所以叫无状态bean。 但无状态会话bean 并非没有状态,如果它有自己的属性(变量),那么这些变量就会受到所有调用它的用户的影响。 每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息,即“有状态”;一旦用户灭亡(调用结束或实例结束),bean的生命期也告结束。 即每个用户最初都会得到一个初始的bean。 示例: 如果该Bean配置为singleton,在并发访问下会出现问题 假设有2个用户user1,user2访问,都调用到了该Bean。 1.当user1 调用到程序中的1步骤的时候,该Bean的私有变量user被付值为user1; 2.理想的状况,当user1走到2步骤的时候,私有变量user应该为user1; 3.但如果在user1调用到2步骤之前,user2开始运行到了1步骤了,由于单态的资源共享,则私有变量user被修改为user2; 4.这种情况下,user1的步骤2用到的()实际用到是user2的对象。 实际应该是这个例子不应该用实例变量,这样就使得这个Bean由无状态变成了有状态Bean。 对于SSH架构的系统,很少关心这方面,因为我们用到的一般都是singleton. Bean的注入由Spring管理。 Struts2中的Action因为会有User这样的实例对象,是有状态信息的,在多线程环境下是不安全的,所以Struts2默认的实现是Prototype模式。 也就是每个请求都新生成一个Action实例,所以不存在线程安全问题。 需要注意的是,如果由Spring管理action的生命周期, scope要配成prototype作用域。 Struts1是基于单例模式实现,也就是只有一个Action实例供多线程使用。 默认的模式是前台页面数据通过actionForm传入,在action中的excute方法接收,这样action是无状态的,所以一般情况下Strunts1是线程安全的。 如果Action中用了实例变量,那么就变成有状态了,同样是非线程安全的。 像下面这样就是线程不安全的。 Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web 容器负责的。 一个Servlet类在Application中只有一个实例存在,有多个线程在使用这个实例。 这是单例模式的应用。 无状态的单例是线程安全的,但我们如果在Servlet里用了实例变量(私有变量),那么就变成有状态了,是非线程安全的。 如下面的用法就是不安全的,因为user是有状态信息的。
免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。