默认情况下,Spring 应用上下文中的 bean 都是单例的,也就是说所有 bean 都只有一个实例。但有时候一些类是易变的,需要保存一些状态,因此重用 bean 是不安全的,此时不能使用单例的 bean。
Spring 定义了多种作用域,可以基于这些作用域创建 bean,包括:
- 单例(Singleton):在整个应用中,只创建 bean 的一个实例。
- 原型(Prototype):每次注入或者通过 Spring 应用上下文获取的时候,都会创建一个新的 bean 实例。
- 会话(Session):在 Web 应用中,为每个会话创建一个 bean 实例。
- 请求(Rquest):在 Web 应用中,为每个请求创建一个 bean 实例。
不同发现和声明 bean 的方法中声明作用域
以原型作用域举例
组件扫描
使用@Scope
注解
@Component
@Scop(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad { ... }
Java 中显性配置
同样使用@Scope
注解
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Notepad notepad() {
return new Notepad();
}
XML 中显性配置
使用元素的 scope 属性来设置作用域
<bean id="notepad" class="com.myapp.Notepad" scope="prototype" />
注意
原型是在每次注入或者通过 Spring 应用上下文获取的时候创建一个新的 bean 实例,如果在一个单例的 bean 中自动注入一个原型作用域的 bean,因为单例的实例创建时会同时创建原型作用域的 bean 并注入,因此后续不会有注入行为,实质上这个原型作用域的 bean 也变成了单例,每次调用时返回的是相同的实例。
解决方法有两种:
- 设置
@Scope
注解中的proxyMode
属性,使用代理,此时创建单例的实例时只会注入原型 bean 的一个代理,具体的在下面介绍@Scope
注解中会详细讲。 - 不使用自动注入,而是在需要的时候手动使用
getBean
获取原型 bean,如上面所说,通过 Spring 应用上下文获取会创建新的实例。
@Scope
注解
一个使用的例子:
@Scope(value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.INTERFACES)
value
(必需)
默认参数:
- 单例:
ConfigurableBeanFactory.SCOPE_SINGLETON
- 原型:
ConfigurableBeanFactory.SCOPE_PROTOTYPE
- 会话:
WebApplicationContext.SCOPE_SESSION
- 请求:
WebApplicationContext.SCOPE_REQUEST
proxyMode
(非必需)
默认参数:
- 代理接口:
ScopedProxyMode.INTERFACES
- 代理具体类:
ScopedProxyMode.TARGET_CLASS
proxyMode
主要解决了其他作用域的 bean 注入到单例 bean 中产生的问题,例如上面提到的原型的问题,还有会话和请求作用域,单例的 bean 会在 Spring 应用上下文加载的时候创建,此时会话和请求作用域的 bean 不存在,要产生了会话或请求才会创建相应的实例,所以不能注入,使用proxyMode
可以先注入对应 bean 的代理来代替。
还有一种情况是希望在同一个单例的实例中,不同情况下,希望注入不同的其他作用域的实例,也要使用proxyMode
。
ScopedProxyMode.INTERFACES
在要注入的 bean 是接口时使用,表明代理实现了这个 bean 的接口。
ScopedProxyMode.TARGET_CLASS
在要注入的 bean 是具体类时使用,此时使用 CGLib 来生成基于类的代理。