Bean的作用域是什么?有哪些作用域?
作者:程序员马丁
在线博客:https://open8gu.com
大话面试,技术同学面试必备的八股文小册,以精彩回答应对深度问题,助力你在面试中拿个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
实现,比如 SimpleTranscationScope
或 SimpleThreadScope
。
问题详解
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 提供了 SimpleTranscationScope
和 SimpleThreadScope
两者默认不启用的作用域,如果你想使其生效,或需要自己实现 Scope
接口自定义一个作用域,那么都需要手动的注册。
你可以获取 ConfigurableBeanFactory
的 registerScope
方法来注册作用域,具体来说,你可以直接通过 ApplicationContext
的 getAutowireCapableBeanFactory
来获取:
@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 会自动将其注册到容器中。