Spring Bean作用域

 
默认情况下,所有的 Spring Bean 都是单例的,也就是说在整个 Spring 应用中, Bean 的实例只有一个。

我们可以在 <bean> 元素中添加 scope 属性来配置 Spring Bean 的作用范围。例如,如果每次获取 Bean 时,都需要一个新的 Bean 实例,那么应该将 Bean 的 scope 属性定义为 prototype,如果 Spring 需要每次都返回一个相同的 Bean 实例,则应将 Bean 的 scope 属性定义为 singleton。

Spring 5 共提供了 6 种 scope 作用域,如下表。

作用范围 描述
singleton 默认值,单例模式,表示在 Spring 容器中只有一个 Bean 实例
prototype 原型模式,表示每次通过 Spring 容器获取 Bean 时,容器都会创建一个新的 Bean 实例。
request 每次 HTTP 请求,容器都会创建一个 Bean 实例。该作用域只在当前 HTTP Request 内有效。
session 同一个 HTTP Session 共享一个 Bean 实例,不同的 Session 使用不同的 Bean 实例。该作用域仅在当前 HTTP Session 内有效。
application 同一个 Web 应用共享一个 Bean 实例,该作用域在当前 ServletContext 内有效。

与 singleton 类似,但 singleton 表示每个 IoC 容器中仅有一个 Bean 实例,而一个 Web 应用中可能会存在多个 IoC 容器,但一个 Web 应用只会有一个 ServletContext,也可以说 application 才是 Web 应用中货真价实的单例模式。
websocket websocket 的作用域是 WebSocket ,即在整个 WebSocket 中有效。

注意:在以上 6 种 Bean 作用域中,除了 singleton 和 prototype 可以直接在常规的 Spring IoC 容器(例如 ClassPathXmlApplicationContext)中使用外,剩下的都只能在基于 Web 的 ApplicationContext 实现(例如 XmlWebApplicationContext)中才能使用,否则就会抛出一个 IllegalStateException 的异常。

本节,我们就只对 singleton 和 prototype 这两种 Bean 作用域进行详细的讲解,至于其他 Bean 作用域,我们会在后续的教程中陆续进行介绍。

singleton

singleton 是 Spring 容器默认的作用域。当 Bean 的作用域为 singleton 时,Spring IoC 容器中只会存在一个共享的 Bean 实例。这个 Bean 实例将存储在高速缓存中,所有对于这个 Bean 的请求和引用,只要 id 与这个 Bean 定义相匹配,都会返回这个缓存中的对象实例。

如果一个 Bean 定义的作用域为 singleton ,那么这个 Bean 就被称为 singleton bean。在 Spring IoC 容器中,singleton bean 是 Bean 的默认创建方式,可以更好地重用对象,节省重复创建对象的开销。

在 Spring 配置文件中,可以使用 <bean> 元素的 scope 属性,将 Bean 的作用域定义成 singleton,其配置方式如下所示:
<bean id="..." class="..." scope="singleton"/>

示例 1

下面我们就通过一个简单的实例,对 Bean 的 singleton 作用域进行演示。

1. 参考《第一个 Spring 程序》,新建一个名为 my-spring-scope-demo 的 Java 项目。

2. 在 my-spring-scope-demo 中,新建一个名为 net.biancheng.c 的包,并在这个包下创建一个名为 SingletonBean 的类,代码如下。
package net.biancheng.c;

public class SingletonBean {
    private String str;

    public void setStr(String str) {
        this.str = str;
    }
}

3. 在 src 目录下创建 Spring 配置文件 Beans.xml,配置如下。
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="singletonBean" class="net.biancheng.c.SingletonBean" scope="singleton">
        <property name="str" value="C语言中文网"></property>
    </bean>

</beans>

4. 在 net.biancheng.c 包下,创建一个名为 MainApp 的类,代码如下。
package net.biancheng.c;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    private static final Log LOGGER = LogFactory.getLog(MainApp.class);

    public static void main(String[] args) {
        //获取 ApplicationContext 容器
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        SingletonBean singletonBean = context.getBean("singletonBean", SingletonBean.class);
        SingletonBean singletonBean2 = context.getBean("singletonBean", SingletonBean.class);
        System.out.println(singletonBean);
        System.out.println(singletonBean2);
    }
}

5. 运行 MainApp 类中的 main() 方法,控制台输出如下。
net.biancheng.c.SingletonBean@65e2dbf3
net.biancheng.c.SingletonBean@65e2dbf3

从控制台的输出可以看出,两次获得的 Bean 实例的地址完全一样,这说明 IoC 容器只创建了一个 singletonBean 实例。由于 singleton 是 Spring IoC 容器的默认作用域,因此即使省略 scope 属性,控制台的输出结果也一样的。

prototype

如果一个 Bean 定义的作用域为 prototype,那么这个 Bean 就被称为 prototype bean。对于 prototype bean 来说,Spring 容器会在每次请求该 Bean 时,都创建一个新的 Bean 实例。

从某种意义上说,Spring  IoC 容器对于 prototype bean 的作用就相当于 Java 的 new 操作符。它只负责 Bean 的创建,至于后续的生命周期管理则都是由客户端代码完成的,详情请参看《Spring Bean 生命周期》。

在 Spring 配置文件中,可以使用 <bean> 元素的 scope 属性将 Bean 的作用域定义成 prototype,其配置方式如下所示:
<bean id="..." class="..." scope="prototype"/>

示例 2

下面我们就通过一个简单的实例,对 Bean 的 prototype 作用域进行演示。

1. 在 net.biancheng.c 包中,创建一个名为 PrototypeBean 的类,代码如下。
package net.biancheng.c;

public class PrototypeBean {
    private String str;

    public void setStr(String str) {
        this.str = str;
    }
}

2. 在 Beans.xml 中,添加 PrototypeBean 的 Bean 的定义,配置如下。
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <!--单例模式 singleton-->
    <bean id="singletonBean" class="net.biancheng.c.SingletonBean" scope="singleton">
        <property name="str" value="C语言中文网"></property>
    </bean>
    <!--原型模式 prototype-->
    <bean id="prototypeBean" class="net.biancheng.c.PrototypeBean" scope="prototype">
        <property name="str" value="c.biancheng.net"></property>
    </bean>
</beans>

3. 对 MainApp 中的代码进行修改。
package net.biancheng.c;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    private static final Log LOGGER = LogFactory.getLog(MainApp.class);

    public static void main(String[] args) {
        //获取 ApplicationContext 容器
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        PrototypeBean prototypeBean = context.getBean("prototypeBean", PrototypeBean.class);
        PrototypeBean prototypeBean2 = context.getBean("prototypeBean", PrototypeBean.class);
        System.out.println(prototypeBean);
        System.out.println(prototypeBean2);
    }
}

4. 执行 MainApp 中的 main 函数,控制台输出如下。
net.biancheng.c.PrototypeBean@61f8bee4
net.biancheng.c.PrototypeBean@7b49cea0

从运行结果可以看出,两次输出的内容并不相同,这说明在 prototype 作用域下,Spring 容器创建了两个不同的 prototypeBean 实例。