Contents

Spring Bean 范围快速指南

1.概述

在这个快速教程中,我们将了解 Spring 框架中不同类型的 bean 作用域。 bean 的范围定义了该 bean 在我们使用它的上下文中的生命周期和可见性。 最新版本的 Spring 框架定义了 6 种作用域:

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

最后提到的四个范围,requestsessionapplicationwebsocket,仅在 web 感知应用程序中可用。

2. 单例范围

当我们使用singleton范围定义 bean 时,容器会创建该 bean 的单个实例;对该 bean 名称的所有请求都将返回相同的对象,该对象被缓存。对对象的任何修改都将反映在对 bean 的所有引用中。如果未指定其他范围,则此范围是默认值。

让我们创建一个Person实体来举例说明作用域的概念:

public class Person {
    private String name;
    // standard constructor, getters and setters
}

之后,我们使用*@Scope注释定义具有singleton*范围的 bean:

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

我们还可以通过以下方式使用常量而不是String值:

@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)

现在我们可以继续编写一个测试,表明引用同一个 bean 的两个对象将具有相同的值,即使它们中只有一个改变了它们的状态,因为它们都引用了同一个 bean 实例:

private static final String NAME = "Ann Emily";
@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文件应包含所用 bean 的 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="com.blogdemo.scopes.Person" scope="singleton"/>
</beans>

3. prototype范围

 每次从容器请求时,具有prototype范围的 bean都会返回不同的实例。它是通过将值prototype设置为 bean 定义中的*@Scope*注解来定义的:

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

我们也可以像在singleton作用域中那样使用常量:

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)

我们现在将编写一个与之前类似的测试,显示两个对象在 prototype范围内请求相同的 bean 名称。它们将具有不同的状态,因为它们不再引用同一个 bean 实例:

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 添加xml定义

<bean id="personPrototype" class="com.blogdemo.scopes.Person" scope="prototype"/>

4. Web 感知范围

如前所述,有四个附加范围仅在 Web 感知应用程序上下文中可用。我们在实践中较少使用这些。

request范围为单个 HTTP 请求创建一个 bean 实例,而session范围为一个 HTTP 会话创建一个 bean 实例。

application范围为ServletContext的生命周期创建 bean 实例,而websocket范围为特定的WebSocket会话创建它。

让我们创建一个用于实例化 bean 的类:

public class HelloMessageGenerator {
    private String message;
    
    // standard getter and setter
}

4.1. Request范围

我们可以使用*@Scope注释定义具有request*范围的bean:

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

proxyMode属性是必要的,因为在 Web 应用程序上下文的实例化时刻,没有活动请求。Spring 创建一个代理作为依赖注入,并在请求中需要它时实例化目标 bean。 我们还可以使用*@RequestScope*组合注释作为上述定义的快捷方式:

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

接下来,我们可以定义一个控制器,该控制器具有对requestScopedBean的注入引用。我们需要两次访问相同的请求以测试 Web 特定范围。

如果我们在每次请求运行时都显示该message,我们可以看到该值被重置为null,即使它后来在方法中被更改。这是因为每个请求都返回了不同的 bean 实例。

@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范围

我们可以用类似的方式定义具有session范围的 bean:

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

还有一个专用的组合注解,我们可以使用它来简化 bean 定义:

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

接下来我们定义一个引用sessionScopedBean的控制器。同样,我们需要运行两个请求以显示message字段的值对于会话是相同的。

在这种情况下,当第一次发出请求时,值messagenull。但是,一旦更改,该值将保留给后续请求,因为为整个会话返回相同的 bean 实例。

@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范围

application范围为ServletContext的生命周期创建 bean 实例。这类似于*singleton *范围,但在 bean 的范围方面有一个非常重要的区别。

当 bean 是application作用域时,bean 的同一个实例在同一个ServletContext中运行的多个基于 servlet 的应用程序之间共享,而singleton 作用域 bean 的作用域仅限于单个应用程序上下文。 让我们创建具有application范围的 bean :

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

类似于*request session *范围,我们可以使用更短的版本:

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

现在让我们创建一个引用这个 bean 的控制器:

@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";
    }
}

在这种情况下,一旦在applicationScopedBean中设置,值message将保留给所有后续请求、会话,甚至对于将访问此 bean 的不同 servlet 应用程序,只要它在相同的ServletContext中运行。

4.4. WebSocket 范围

最后,让我们使用websocket范围创建 bean :

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

首次访问时,WebSocket范围的 bean 存储在WebSocket会话属性中。每当在整个WebSocket会话期间访问该 bean 时,都会返回该 bean 的相同实例。

我们也可以说它表现出单例行为,但仅限于WebSocket会话。