理解 Spring Bean 的作用域(Scope)

简介: 理解 ScopeScope 表示 Spring bean 的作用范围,指明了 bean 的生命周期。

理解 Scope


Scope 表示 Spring bean 的作用范围,指明了 bean 的生命周期。


Spring 中有哪些 Scope


Spring 中内置了一些 Scope,并且用户还能够扩展自己的 Scope,Spring 中内置的 Scope 如下,它们分别用在不同的场景中。


image.png

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


目录
相关文章
|
4天前
|
安全 Java Spring
Spring框架中的单例Bean是线程安全的吗?
Spring框架中的单例Bean是线程安全的吗?
10 1
|
4天前
|
XML 前端开发 Java
【JavaEE】深入了解Spring中Bean的可见范围(作用域)以及前世今生(生命周期)
【JavaEE】深入了解Spring中Bean的可见范围(作用域)以及前世今生(生命周期)
7 0
|
4天前
|
存储 缓存 Java
【JavaEE】Spring中注解的方式去获取Bean对象
【JavaEE】Spring中注解的方式去获取Bean对象
3 0
|
4天前
|
存储 Java 对象存储
【JavaEE】Spring中注解的方式去存储Bean对象
【JavaEE】Spring中注解的方式去存储Bean对象
7 0
|
4天前
|
存储 Java 对象存储
【JavaEE】DI与DL的介绍-Spring项目的创建-Bean对象的存储与获取
【JavaEE】DI与DL的介绍-Spring项目的创建-Bean对象的存储与获取
9 0
|
4天前
|
消息中间件 安全 Java
在Spring Bean中,如何通过Java配置类定义Bean?
【4月更文挑战第30天】在Spring Bean中,如何通过Java配置类定义Bean?
21 1
|
4天前
|
XML Java 数据格式
Spring Bean
【4月更文挑战第30天】Spring Bean
17 0
|
4天前
|
前端开发 Java 数据格式
【Spring系列笔记】定义Bean的方式
在Spring Boot应用程序中,定义Bean是非常常见的操作,它是构建应用程序的基础。Spring Boot提供了多种方式来定义Bean,每种方式都有其适用的场景和优势。
32 2
|
4天前
|
XML Java 数据格式
谈谈 Spring 中 Bean 的生命周期?
谈谈 Spring 中 Bean 的生命周期?
20 1
|
Java Spring 容器
如何在 Spring 中自定义 scope
大家对于 Spring 的 scope 应该都不会默认。所谓 scope,字面理解就是“作用域”、“范围”,如果一个 bean 的 scope 配置为 singleton,则从容器中获取 bean 返回的对象都是相同的;如果 scope 配置为prototype,则每次返回的对象都不同。
2145 0