1. 概述

本文快速讲解Spring框架中不同类型的Bean作用域(scope)。作用域定义了Bean的生命周期和可见性范围。最新版Spring框架定义了6种作用域:

  • singleton
  • prototype
  • request
  • session
  • application
  • websocket

后四种(request、session、application、websocket)仅适用于Web应用。

2. 单例作用域(Singleton Scope)

当Bean定义为单例作用域时,容器只会创建一个实例。所有对该Bean的请求都返回同一个被缓存的对象。任何对象修改都会在所有引用中生效。这是默认作用域。

创建Person实体类示例:

public class Person {
    private String name;

    // 标准构造器、getter和setter
}

使用@Scope注解定义单例Bean:

@Bean
@Scope("singleton")
public Person personSingleton() {
    return new Person();
}

也可以用常量替代字符串:

@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)

测试代码验证单例特性:两个引用对象指向同一实例,修改其中一个会影响另一个:

private static final String NAME = "John Smith";

@Test
public void givenSingletonScope_whenSetName_thenEqualNames() {
    ApplicationContext applicationContext = 
      new ClassPathXmlApplicationContext("scopes.xml");

    Person personSingletonA = (Person) applicationContext.getBean("personSingleton");
    Person personSingletonB = (Person) applicationContext.getBean("personSingleton");

    personSingletonA.setName(NAME);
    Assert.assertEquals(NAME, personSingletonB.getName());

    ((AbstractApplicationContext) applicationContext).close();
}

scopes.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<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.xsd">

    <bean id="personSingleton" class="org.baeldung.scopes.Person" scope="singleton"/>    
</beans>

3. 原型作用域(Prototype Scope)

原型作用域每次请求都返回新实例。通过设置@Scope("prototype")定义:

@Bean
@Scope("prototype")
public Person personPrototype() {
    return new Person();
}

同样可用常量:

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)

测试代码验证原型特性:两个请求返回独立实例,状态互不影响:

private static final String NAME = "John Smith";
private static final String NAME_OTHER = "Anna Jones";

@Test
public void givenPrototypeScope_whenSetNames_thenDifferentNames() {
    ApplicationContext applicationContext = 
      new ClassPathXmlApplicationContext("scopes.xml");

    Person personPrototypeA = (Person) applicationContext.getBean("personPrototype");
    Person personPrototypeB = (Person) applicationContext.getBean("personPrototype");

    personPrototypeA.setName(NAME);
    personPrototypeB.setName(NAME_OTHER);

    Assert.assertEquals(NAME, personPrototypeA.getName());
    Assert.assertEquals(NAME_OTHER, personPrototypeB.getName());

    ((AbstractApplicationContext) applicationContext).close();
}

scopes.xml添加原型Bean定义:

<bean id="personPrototype" class="org.baeldung.scopes.Person" scope="prototype"/>

4. Web相关作用域

四种Web专用作用域实际使用较少:

  • request:单次HTTP请求创建实例
  • session:HTTP会话创建实例
  • application:ServletContext生命周期内创建实例
  • websocket:WebSocket会话创建实例

创建测试类:

public class HelloMessageGenerator {
    private String message;
    
    // 标准getter和setter
}

4.1. 请求作用域(Request Scope)

定义请求作用域Bean:

@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator requestScopedBean() {
    return new HelloMessageGenerator();
}

proxyMode是必要的,因为Web应用上下文初始化时无活跃请求。Spring创建代理注入依赖,实际请求时才创建目标Bean。

也可用快捷注解:

@Bean
@RequestScope
public HelloMessageGenerator requestScopedBean() {
    return new HelloMessageGenerator();
}

控制器测试:每次请求返回新实例,状态重置:

@Controller
public class ScopesController {
    @Resource(name = "requestScopedBean")
    HelloMessageGenerator requestScopedBean;

    @RequestMapping("/scopes/request")
    public String getRequestScopeMessage(final Model model) {
        model.addAttribute("previousMessage", requestScopedBean.getMessage());
        requestScopedBean.setMessage("Good morning!");
        model.addAttribute("currentMessage", requestScopedBean.getMessage());
        return "scopesExample";
    }
}

4.2. 会话作用域(Session Scope)

定义会话作用域Bean:

@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator sessionScopedBean() {
    return new HelloMessageGenerator();
}

快捷注解方式:

@Bean
@SessionScope
public HelloMessageGenerator sessionScopedBean() {
    return new HelloMessageGenerator();
}

控制器测试:同一会话内保持状态:

@Controller
public class ScopesController {
    @Resource(name = "sessionScopedBean")
    HelloMessageGenerator sessionScopedBean;

    @RequestMapping("/scopes/session")
    public String getSessionScopeMessage(final Model model) {
        model.addAttribute("previousMessage", sessionScopedBean.getMessage());
        sessionScopedBean.setMessage("Good afternoon!");
        model.addAttribute("currentMessage", sessionScopedBean.getMessage());
        return "scopesExample";
    }
}

4.3. 应用作用域(Application Scope)

应用作用域在ServletContext生命周期内创建实例。类似单例作用域,但关键区别在于:

  • application作用域:同一ServletContext内的多个Servlet应用共享实例
  • singleton作用域:仅限于单个应用上下文

定义应用作用域Bean:

@Bean
@Scope(
  value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator applicationScopedBean() {
    return new HelloMessageGenerator();
}

快捷注解方式:

@Bean
@ApplicationScope
public HelloMessageGenerator applicationScopedBean() {
    return new HelloMessageGenerator();
}

控制器测试:所有请求/会话/应用共享状态:

@Controller
public class ScopesController {
    @Resource(name = "applicationScopedBean")
    HelloMessageGenerator applicationScopedBean;

    @RequestMapping("/scopes/application")
    public String getApplicationScopeMessage(final Model model) {
        model.addAttribute("previousMessage", applicationScopedBean.getMessage());
        applicationScopedBean.setMessage("Good afternoon!");
        model.addAttribute("currentMessage", applicationScopedBean.getMessage());
        return "scopesExample";
    }
}

4.4. WebSocket作用域

定义WebSocket作用域Bean:

@Bean
@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator websocketScopedBean() {
    return new HelloMessageGenerator();
}

首次访问时,Bean存储在WebSocket会话属性中。整个WebSocket会话期间返回同一实例(类似单例,但仅限于当前会话)。

5. 总结

本文详细讲解了Spring提供的各种Bean作用域及其适用场景。单例和原型是基础作用域,而四种Web作用域则针对特定Web场景设计。选择正确的作用域能避免很多并发问题,踩坑时记得检查作用域配置。

完整示例代码可在GitHub项目中查看。


原始标题:Quick Guide to Spring Bean Scopes