理解 Spring Bean 的作用域(Scope)

理解 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 的刷新,感兴趣的小伙伴可自行查阅相关资料。

猜你喜欢

转载自blog.csdn.net/zzuhkp/article/details/108569279