理解 Scope
Scope 表示 Spring bean 的作用范围,指明了 bean 的生命周期。
Spring 中有哪些 Scope
Spring 中内置了一些 Scope,并且用户还能够扩展自己的 Scope,Spring 中内置的 Scope 如下,它们分别用在不同的场景中。
作用域 | 描述 |
---|---|
singleton | 单例,Spring Framework 默认的作用域,未配置或配置的 scope 为 “” 会使用 singleton 作用域。在某一个 Spring 容器中,对于同一个 bean 定义,只会有一个对象。Spring 会对单例 bean 进行缓存,管理单例 bean 的完整生命周期。 |
prototype | 原型,和 singleton 一样是原生支持的作用域,在某一个 Spring 容器中,对于同一个 bean 定义,每次获取时都会获取到一个新的对象。原型对象在 Spring 中不具备完整的生命周期,Spring 只会负责原型对象的实例化和初始化,销毁回调需要由用户自行完成。 |
request | 请求,用于 web 环境,表示在某一次请求中,对于同一个 bean 定义,只会有一个对象。 |
session | 会话,用于 web 环境,表示在某一个会话中,对于同一个 bean 定义,只会有一个 对象。 |
application | 应用,用于 web 环境,表示某一个 web 应用中,对于同一个 bean 定义,只会有一个对象,web 环境下和 singleton 语义一致。 |
websocket | 用于 web 环境 |
Spring 中的 Scope 大致可以分为两类,一类是在任何环境下都支持的 singleton、prototype,另一类是在 web 环境下才支持的 request、session、application、websocket。web 环境下的 scope 都是由 Spring 自定义实现。
如何配置 Scope
Spring 配置元数据包括 xml、properties、注解等,其中注解现在使用最多。scope 是 bean 定义的一个属性,xml 和 注解使用方式分别如下。
xml 配置 Scope
xml 是 Spring 早期使用较多,配置方式如下。
<bean class="com.mypackage.MyBean" scope="">
<aop:scoped-proxy proxy-target-class="false" />
</bean>
bean 标签中的 scope 属性用来指定作用域。
注解配置 Scope
Spring 中存在一个名为 @Scope 的注解,完整限定符为org.springframework.context.annotation.Scope
,@Scope 可以配置在表示 bean 的类或方法上。使用方式如下。
@Scope(scopeName = "", proxyMode = ScopedProxyMode.DEFAULT)
@Component
public class Config {
@Bean
@Scope(scopeName = "", proxyMode = ScopedProxyMode.DEFAULT)
public Object bean() {
return new Object();
}
}
scopeName 表示 scope 的名称。proxyMode 表示代理方式,根据配置可能会生成代理对象,可用于在 singleton bean 中注入非 singleton bean。
自定义 Scope
Spring 对自定义 Scope 的使用
scope 主要影响 Spring 对 bean 的获取。AbstractBeanFactory#doGetBean
方法获取 bean 时有关 Scope 的部分代码如下。
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
...省略部分代码
// Create bean instance.
if (mbd.isSingleton()) {
//获取singleton bean
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
//获取 prototype bean
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
} finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
//获取自定义scope的bean
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
} finally {
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
} catch (IllegalStateException ex) {
... 省略部分代码
}
}
... 省略部分代码
}
- Spring 在获取 bean 时,先判断 scope 是否为singleton,如果是则获取单例对象。
- 否则判断 scope 是否为 prototype 类型,如果是则会创建一个新对象。
- 如果不是 singleton 或 prototype ,Spring 会从自定义的 scope 中获取 bean。
Spring BeanFactory 使用了一个 map 保存注册的 Scope,和 @Scope 注解相同名称的 Scope 是一个接口,其完整的限定名为org.springframework.beans.factory.config.Scope
。web 环境中的 Scope 值 request、session、application 均是使用自定义的 Scope 实现,它们分别为 RequestScope、SessionScope 和 ServletContextScope。为了向 Spring 中添加自定义的 Scope ,需要调用 ConfigurableBeanFactory#registerScope
接口进行注册。
Scope 接口定义
先看 Scope 接口的定义。
public interface Scope {
// 获取 Scope 中保存的 bean
Object get(String name, ObjectFactory<?> objectFactory);
// 移除 Scope 中保存的 bean
@Nullable
Object remove(String name);
// 注册 bean 销毁时的回调
void registerDestructionCallback(String name, Runnable callback);
// 解析上下文中的 key,web 环境下有使用
@Nullable
Object resolveContextualObject(String key);
// 获取会话 ID,web 环境下有使用
@Nullable
String getConversationId();
}
Scope 中保存了作用域范围内的 bean,并提供了获取、移除、注册销毁回调等方法。
总结
Spring 中原生支持 singleton、prototype 作用域,web 环境下 Spring 通过自定义作用域实现了 request、session、application。通过自定义 Scope,Spring Cloud 甚至实现了 @Value 的刷新,感兴趣的小伙伴可自行查阅相关资料。