Spring中Bean的作用域

一、Bean作用域的种类

通过spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下几种作用域:

  • singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例

  • prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例

  • request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。

  • session:对于每次HTTP Session,使用session定义的Bean都将产生一个新实例。

  • globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。

  • application

  • websocket

除了singleton和 prototype模式以外,剩下的模式只有在Web应用中使用Spring时,该作用域才有效

二、详细介绍

2.1 singleton

singleton(单例)是Spring 中 Bean默认的作用域。若一个bean的作用域是单例的,那么每个IoC容器只会对该bean创建一个实例对象。所有对这个bean的依赖,以及获取这个bean的代码,拿到的都是同一个bean实例。Spring容器在创建这个bean后,会将它缓存在容器中(放在一个ConcurrentHashMap中)。Spring中的bean不是线程安全的,所以只有在我们只关注bean能够提供的功能,而不在意它的状态时,才应该使用这个作用域。

注意:

这里所说的单例,和设计模式中所提到的单例模式不同。设计模式中的单例,是强制一个类有且只有一个对象,我们如果不通过特殊的手段,将无法为这个单例类创建多个对象。而Spring中的单例作用域不同,这里的单例指的是在一个Spring容器中,只会缓存bean的唯一对象,所有通过容器获取这个bean的方式,最终拿到的都是同一个对象。但是在不同的Spring容器中,每一个Spring容器都可以拥有单例bean的一个实例对象,也就是说,这里的单例限定在一个Spring容器中,而不是整个应用程序。并且我们依然可以通过new的方式去自己创建bean。

2.2 prototype

prototype 可以理解为多例。若一个bean的作用域是prototype,那么Spring容器并不会缓存创建的bean,程序中每一次获取这个bean时,容器都会重新实例化一个bean对象。通常,如果我们需要使用bean的状态(属性),且这个状态是会改变的,那么我们就可以将它配置为这个作用域,以解决线程安全的问题。因为对于单例bean来说,多个线程共享它的可变属性,会存在线程安全问题。

如果bean的作用域是prototype的,那么容器在创建完这个bean后,并不会将它保存在容器中,这也就意味着,Spring容器并不能为我们做这个对象的销毁工作。此时我们可以通过Spring提供的接口,自定义一个后处理器,然后将这些bean的引用存储在这个后处理器中,当容器回调这个后处理器的方法时,我们可以在方法中通过提前存储的bean的引用,将它们销毁。

2.3 request

request 作用域将bean的使用范围限定在一个http请求中,对于每一个请求,都会单独创建一个bean,若请求结束,bean也会随之销毁。使用request作用域一般不会存在线程安全问题,因为在Web应用中,每个请求都是由一个单独的线程进行处理,所有线程之间并不会共享bean,从而不会存在线程安全的问题。

这个作用域只能使用在Web应用中。如果使用的是注解扫描配置bean,那么在bean所属的类上使用@RequestScope注解即可使用此作用域,若是基于xml文件,则通过bean的scope配置项:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="request"/>

2.4 session

session 作用域将bean的使用范围限定在一次http会话中,对于每一个会话,Spring容器都会创建一个单独的bean,若session被销毁,则bean也随之销毁。我们可以修改bean的状态,这个修改只对当前会话可见,但是是否线程安全呢?Spring文档中并未提及,但我认为不是线程安全的,因为每一个session可以对应于多个request,这些请求不一定就是串行执行的,比如说用户打开多个界面,同时进行多次操作,那后台将同时处理同一个session的多个request,此时并不能保证bean的线程安全。

与request作用域一样,session作用域只能使用在Web应用中。可以使用@SessionScope将bean指定为session作用域,或xml配置方式:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

2.6 globalSession

globalSession 作用域的效果与session作用域类似,但只适用于基于portlet的web应用程序中。Portlet规范定义了globalSession的概念,该概念在组成单个Portlet Web应用程序的所有Portlet之间共享。Spring文档中提到,如果在基于Servlet的web应用程序中使用globalSession作用域,实际上容器使用session作用域进行处理。

该作用域也只在web应用中有效,具体是在基于portlet的应用中有效。使用方式基于xml:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>

2.7 application

学过Servlet的应该对application作用域有所了解,在Servlet程序中,有一个全局的ServletContext对象,这个对象被整个web应用所共享,可以通过setAttribute方法向其中添加全局共享的数据。而Spring中,application作用域就是这么实现的,作用域为application的bean,将会被作为ServletContext的属性,存储在其中,然后可以被全局访问,而且一个ServletContext只会存储这个bean的一个实例对象。ServletContext被销毁,这个bean自然也跟着被销毁。这好像有点类似于singleton这个作用域,但是也有一些区别。单例bean是一个Spring只会创建一个,而这里的却是每个ServletContext包含一个,不论有多少Spring容器,bean的数量只取决于ServletContext,而且单例bean只能通过容器去获取,是隐式的,而这种作用域的bean却是公开的,存储在ServletContext中,可直接通过ServletContext获取。

application作用域也只能用于web应用中。可以通过@ApplicationScope注解,或xml配置文件:

<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>

2.8 websocket

websocket 是一种应用层的通信协议,它提供应用层的全双工通信。而Spring提供对websocket协议的支持,于是就有了这么一个作用域。在Spring官方文档中并没有对这个作用域进行详细描述,可以通过名称推断出来。若一个bean的作用域为websocket,则只作用于一次websocket通信,若连接被释放,则bean自然也会被销毁。

2.9 单例Bean依赖于多例Bean

在我们的应用程序中,可能有这样一种情况,一个作用域为singleton的bean,有一个或多个作用域为prototype的bean,此时将会发生什么问题。对于单例bean来说,属性注入只会发生在创建这个bean的过程中,这也就意味着,单例bean只会经历一次属性注入。也就是说,尽管这个单例bean的属性是多例的,但是由于只有一次注入,所以后续使用到的这个多例属性,永远都是同一个。此时多例就失去了意义。那该如何解决呢?

方法主要有两种:

  1. 方法一:对于单例bean的多例属性,我们不让Spring容器帮我们自动注入,而是我们自己编写一个工厂方法,在方法中通过getBean等方式,手动地向容器请求这个多例bean。由于bean是多例的,每一次getBean,实际上返回的都是一个新的实例对象。而在单例bean需要用到这个多例bean时,通过工厂方法获取。但是这种方式比较麻烦,也不利于维护。

  2. 方法二:使用Spring提供了的方法注入机制解决这个问题。

三、总结

比较常用的是singleton和prototype两种作用域。对于singleton作用域的Bean,每次请求该Bean都将获得相同的实例。容器负责跟踪Bean实例的状态,负责维护Bean实例的生命周期行为;如果一个Bean被设置成prototype作用域,程序每次请求该id的Bean,Spring都会新建一个Bean实例,然后返回给程序。在这种情况下,Spring容器仅仅使用new 关键字创建Bean实例,一旦创建成功,容器不在跟踪实例,也不会维护Bean实例的状态。

如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。

posted @ 2023-10-01 12:14  杨业壮  阅读(22)  评论(0编辑  收藏  举报