LinkedBear
LinkedBear
Published on 2025-05-19 / 7 Visits
0
0

Spring中Bean的作用域

回忆作用域的概念

作用域这个概念我们从一门编程语言的学习开始就会接触到,以我们熟悉的 Java 为例,一个类中通常会包含一些成员变量、方法变量、局部变量,下面的代码片段中就是非常经典的作用域区分案例。对于基础扎实的读者而言,代码片段中的问题不难回答,四个问题中可访问的成员作用域级别依次提升,这也说明对于不同的作用域,可访问的位置是不同的

 public class ScopeReviewDemo {
     // 类级别成员
     private static String classVariable = "";
     
     // 对象级别成员
     private String objectVariable = "";
     
     public static void main(String[] args) throws Exception {
         // 方法级别成员
         String methodVariable = "";
         for (int i = 0; i < args.length; i++) {
             // 循环体局部成员
             String partVariable = args[i];
             
             // 此处能访问哪些变量?
         }
         
         // 此处能访问哪些变量?
     }
     
     public void test() {
         // 此处能访问哪些变量?
     }
     
     public static void staticTest() {
         // 此处能访问哪些变量?
     }
 }

SpringFramework的作用域

SpringFramework 中内置的作用域包含 6 种,这是从 4.x 开始就一直延续至今的设计,简单的对比如下表所示。对于普通的应用而言,我们通常都会只用到 singleton 和 prototype 。

作用域类型

概述

singleton

一个 IOC 容器中只有一个【默认值】

prototype

每次获取创建一个

request

一次请求创建一个(仅Web应用可用)

session

一个会话创建一个(仅Web应用可用)

application

一个 Web 应用创建一个(仅Web应用可用)

websocket

一个 WebSocket 会话创建一个(仅Web应用可用)

Singleton:单实例

在 Spring Framework 的官方文档中找到讲解 Bean Scope 的章节,其中分别介绍了单实例与原型的作用域。下图是对应章节中单实例的片段中附带的一张图,这张图非常经典,左边的几个定义的 Bean 同时引用了右边的同一个 accountDao ,这个 accountDao 就是单实例 Bean 。

Prototype:原型

原型 Bean 的章节中也有一张类似的图,Spring 官方对于原型的定义是:每次对原型 Bean 提出获取请求时,都会创建一个新的 bean 对象。这里提到的“提出获取请求”,包括任何的依赖查找、依赖注入动作。由此也可以总结出一点:如果连续两次调用 getBean 方法,那么应当创建两个不同的 bean 对象;向两个不同的 bean 对象中注入两次,也应当注入两个不同的 bean 对象。

Web应用作用域

除了两个常用的作用域之外,还涉及到几个关于 Web 应用的作用域:

  • request :请求 Bean ,每次客户端向 Web 应用服务器发起一次请求,Web 服务器接收到请求后由 Spring Framework 生成一个 Bean ,直到请求结束。

  • session :会话 Bean ,每个客户端在与 Web 应用服务器发起会话后,Spring Framework 会为之生成一个 Bean ,直到会话过期。

  • application :应用 Bean ,每个 Web 应用在启动时,Spring Framework 会生成一个 Bean ,直到应用停止(有的也叫 global-session )。

  • websocket :WebSocket Bean ,每个客户端在与 Web 应用服务器建立 WebSocket 长连接时,Spring Framework 会为之生成一个 Bean ,直到断开连接。

一点小拓展

原型Bean依赖单实例Bean

原型 Bean 中如果依赖了单实例 Bean ,那么原型 Bean 创建时可以正常从 IOC 容器中得到这些单实例 Bean ,也就是说,原型 Bean 在依赖其他单实例 Bean 时,创建出来的原型 Bean 是正常的。

单实例Bean依赖原型Bean

相反,如果单实例 Bean 想要依赖原型 Bean ,就不能像依赖单实例 Bean 那样使用常规方法,而是需要通过依赖 ApplicationContext ,每次使用原型 Bean 时主动从 ApplicationContext 中获取(即依赖查找)。究其原因,由于单实例 Bean 只会创建一次,因此使用 @Autowired 等注解注入原型 Bean 时,也就相当于仅触发了一次原型 Bean 的创建,此后使用的 bean 对象都是当时创建的那一个,而不是每次都使用全新的。

不同作用域的循环依赖问题

IOC 容器只能解决单实例 Bean 的循环依赖问题,对于原型 Bean 的循环依赖问题无法解决,具体原因可参照《SpringBoot源码解读与原理分析》的 7.15.5 节。


Comment