默认情况下,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 来生成基于类的代理。

最后修改:2023 年 06 月 05 日
如果觉得我的文章对你有用,请随意赞赏