Skip to main content

Bean的作用域是什么?有哪些作用域?

作者:程序员马丁

在线博客:https://open8gu.com

note

大话面试,技术同学面试必备的八股文小册,以精彩回答应对深度问题,助力你在面试中拿个offer。

回答话术

1. 什么是 Scope?

作用域即 Bean 的 Scope,它表示被 Spring 容器管理的 Bean 的生命周期。

简单的来说,就是一个 Bean 在什么范围内有效。比如一个 Bean 只在 Request 范围内有效,那么一次请求完毕后将无法再从 Spring 容器中获得它。

我们看一下它在源码中是如何使用的,就能理解它时怎么生效的:

protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// ... ...

// 在 scope 中创建 bean
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});

// ... ...
}

2. 如何使用

我们可以在通过 @Scope 注解显式的指定 Bean 作用域:

@Scope("singleton") // 指定作用域为单例
@Component
public class Foo {

@Scope("prototype") // 指定作用域为多例
@Bean
public Foo2 foo2() {
return new Foo2();
}
}

3. Spring 支持哪些 Scope?

Spring 默认支持两种作用域:

  • 单例 singleton:对应常量 ConfigurableBeanFactory.SCOPE_SINGLETON,我们的 Bean 默认都是单例的。
  • 原型(多例) prototype:对应常量 ConfigurableBeanFactory.SCOPE_PROTOTYPE

在 SpringWeb 中,还支持三种作用域:

  • 请求 request:对应 WebApplicationContext.SCOPE_REQUEST,即仅在一次请求中有效,另有一个对应组合注解 @RequestScope
  • 会话 session:对应 WebApplicationContext.SCOPE_SESSION,即仅在一次浏览器 Session 中有效,另有一个对应组合注解 @RequestSession
  • 应用 application:对应 WebApplicationContext.SCOPE_APPLICATION,即全局有效,另有一个对应组合注解 @RequestApplication

而在 SpringCloud 中,又额外的支持了一种作用域:

  • 配置刷新 refresh:对应 RefreshScope 类,当 ContextRefreshedEvent 事件发布(即配置中心的配置刷新)时将 Bean 销毁。

此外,还有一些默认不起用的 Scope 实现,比如 SimpleTranscationScopeSimpleThreadScope

问题详解

1. 作用域与依赖注入

当指定了非单例的作用域,又使用了依赖注入时,需要注意:

prototype 为例,我们一般说的“多例”指的是“每次从容器获取 Bean 时都创建一个新的”,如果你已经向某个 Bean 依赖注入了多例的 Bean,那么一般情况下是它是不会自动更新的(除非被依赖注入的 Bean 本身重新初始化了,比如指定为 @RefreshScope 后刷新),比如:

@Component
public class Example {

@Autowired
private Foo foo; // 除非 Example 被重新初始化,否则注入的成员变量 foo 是不会变的
}

如果你需要实现多例的效果,你需要配合上文提到的 @Lookup ,从被 @Lookup 注解的工厂方法、或者直接从 Spring 容器重新获取一个新的 Bean。

比如,改成这样:

public class Example {

@Autowired
private ApplicationContext context;

public void run() {
Assert.isNotSame(context.getBean(Foo.class), context.getBean(Foo.class)); // = false
}
}

2. JSR330 的 @Singleton

顺带一提,JSR330 规定了一个 @Singleton 注解,Spring 实际上也是支持的,由于 Spring 默认的 Bean 都是单例的,因此它其实等效于 @Component

3. 如何注册作用域?

在上文我们提到,Spring 提供了 SimpleTranscationScopeSimpleThreadScope两者默认不启用的作用域,如果你想使其生效,或需要自己实现 Scope 接口自定义一个作用域,那么都需要手动的注册。

你可以获取 ConfigurableBeanFactoryregisterScope方法来注册作用域,具体来说,你可以直接通过 ApplicationContextgetAutowireCapableBeanFactory 来获取:

@Component
public class Example {
@Autowired
private ApplicationContext context;

public void run() {
AutowireCapableBeanFactory acbf = context.getBeanFactory();
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory)acbf;
cbf.register("thread", new SimpleThreadScope()); // 注册名称为 thread 的作用域
}
}

虽然返回值是 AutowireCapableBeanFactory,但是实际类型基本都是 DefaultListableBeanFactory,你可以直接强转成 ConfigurableBeanFactory

或者,你也可以像 RefreshScope 那样,直接把你的自定义作用域对象声明为 Bean:

@Configuration
public class ScopeConfig {

@Bean("thread")
public SimpleThreadScope simpleThreadScope() {
return new SimpleThreadScope();
}
}

Spring 会自动将其注册到容器中。