Spring bean 的作用域

在 Spring 中,那些由 IoC 容器所管理的对象被称之为 bean。而一个 bean 的定义,其实只是一个 “蓝图”,指导着 Spring 如何去创建这样一个 bean。而在这个蓝图中,有一个属性叫做 “作用域”,它规定了这个 bean 的可见范围。这里我们看一下 Spring 的 bean 都有哪些作用域。

支持的作用域

我们先来看一下 Spring 支持哪些作用域。

作用域 说明
singleton 在 Spring 容器中仅存在一个 bean 的实例,bean 以单例形式存在。这是默认的作用域
prototype 每次从容器中获取 bean 时,都将生成一个新的实例,即相当于每次都执行 new xxxBean()
request 在 HTTP 请求 (request) 的完整生命周期中,将创建并使用单个实例。该作用域仅适用于 WebApplicatonContext 环境
session 在 HTTP 会话 (session) 的完整生命周期中,将创建并使用单个实例。该作用域仅适用于 WebApplicationContext 环境
globalSession 在全局的 HTTP 会话 (session) 的完整生命周期中,将创建并使用单个实例。该作用域仅适用于 WebApplicationContext 环境,且通常只能用在 Portlet 环境中。
application ServletContext 的完整生命周期中,将创建并使用单个实例。该作用域仅适用于 WebApplicationContext 环境
websocket WebSocket 的完整生命周期中,将创建并使用单个实例。该作用域仅适用于 WebApplicationContext 环境

指定 bean 的作用域

要指定一个 bean 的作用域,我们可以通过 XML 的方式或注解的方式来设定。

使用 XML 指定配置 bean 时,可以通过 scope 属性来指定作用域:

1
<bean id="someBean" class="com.demo.SomeClass" scope="singleton"/>

使用注解方式配置 bean 时,可以通过 @Scope 注解来指定作用域:

1
2
3
4
5
@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class SomeClass {
// Class definitions goes here
}

此外,如果使用注解方式配置作用域,Spring 也提供了一系列常量值来方便我们配置:

1
2
3
4
5
6
7
8
// 在ConfigurableBeanFactory类中
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";

// 在WebApplicationContext类中
String SCOPE_REQUEST = "request";
String SCOPE_SESSION = "session";
String SCOPE_APPLICATION = "application";

singleton 作用域

singleton 是 Spring 容器中的默认作用域。这个作用域下,容器中只创建各管理一个 bean 实例,实例存在于缓存中,并在后续对该 bean 的请求中都返回这个实例。

prototype 作用域

singleton 正相反,每次对 prototype 作用域的 bean 的请求,Spring 都会生成一个新的实例,即类似我们手动使用 new XxxBean() 方式创建实例。

需要注意的是,Spring 不会完整的管理一个 prototype 的 bean 的生命周期。容器在初始化、配置,并将 bean 交由请求方 (client) 之后,就撒手不管了。也就是说,在销毁一个 prototype 的 bean 时,销毁 bean 的回调方法是不会被调用的,所以在销毁一个 prototype 的 bean 时,开发者必须手动释放它所使用的资源,或者可以尝试使用一个自定义的 bean post-processor来让 Spring 做这些事。

对于有状态的 bean,应当使用 prototype 作用域;对于无状态的 bean,则应当使用 singleton 作用域。

向 singleton bean 注入 prototype bean

因为 bean 的依赖关系在实例化 bean 时才会被解析,所以通常来说,我们不可以将一个 prototype bean 注入到一个 singleton bean 中。

如果我们向一个 singleton bean 中注入一个 prototype bean,因为这个 singleton bean 只会被实例化一次,使得它的依赖也只会被注入一次,最终导致它依赖的那个 singleton bean 也只存在一个实例。

request、session、global session、application 和 websocket 作用域

这几种作用域只能用在 web-aware 的 Spring 上下文中,比如 XmlWebApplicationContext。如果用在一般的 IoC 容器中,比如 ClassPathXmlApplicationContext 中,那么容器会抛出一个 IllegalStateException

要使用这几个作用域,你可能需要对你的应用进行一些配置。因为这些内容与本文无关,所以在这里就不详细说明了。感兴趣的话可以看 Spring 参考手册中的内容

注:web-aware 这个词,我也不知道怎么翻译才合适。查阅了一些资料之后,感觉一个 web-aware 的 Spring 应用就是一个运行在 web 容器 (比如 Tomcat) 中的应用,因为上面提到的这些作用域也是与 web 应用相关的。如果有好的理解,请一定在留言区写下来让在下知道。

request 作用域

request 作用域下的 bean,在每次 HTTP 请求中,都会创建一个新的实例。当请求完成时,对应的 bean 就会被销毁。对一个实例的任何更改,对其他的所有实例来说都是不可见的。

session 作用域

session 作用域下的 bean,在每个活动的 HTTP 会话中,都有一个独自的实例,而当会话结束后,对应的 bean 就会被销毁。对一个实例的任何更改,对其他所有的实例来说都是不可见的。

globalSession 作用域

这个作用域只能用在 portlet 应用中。一个 portlet 站点中可能有多个 portlet 应用,而它们相关的 session 中都会共享同一个 globalSession 作用域的 bean。

注:其实我也不知道 portlet 到底是个啥,就算看过维基百科的 Portlet 条目也没看明白。

application 作用域

在整个应用范围内,容器为每个 web 应用程序运行时创建一个实例。这个作用域与 singleton 很类似,但是还是有两个不同点:

  • 在不同 ServletContext 中有不同的 bean 单例对象;singleton 作用域的 bean 是每个 ApplicationContext 的单例对象。而一个应用可能有多个 ApplicationContext
  • bean 作为 ServletContext 属性可见

[^1]: Bean Scopes - The IoC container
[^2]: Spring 系列四:Bean Scopes 作用域
[^3]: Spring 学习(十五)Spring Bean 的 5 种作用域介绍