知識庫 / Spring RSS 訂閱

Spring Bean 作用域快速指南

Spring
HongKong
4
02:50 PM · Dec 06 ,2025

1. 概述

在本快速教程中,我們將學習 Spring 框架中不同類型的 Bean 作用域。

Bean 的作用域定義了 Bean 在我們使用的上下文中,它的生命週期和可見性。

最新版本的 Spring 框架定義了 6 種作用域類型:

  • 單例 (singleton)
  • 原型 (prototype)
  • 請求 (request)
  • 會話 (session)
  • 應用程序 (application)
  • WebSocket

最後四種提到的作用域,即 請求、會話、應用程序WebSocket,僅適用於面向 Web 的應用程序。

2. 單例作用域

當我們定義具有 單例 作用域的 Bean 時,容器會創建一個該 Bean 的單個實例;所有對該 Bean 名稱的請求都將返回相同對象,該對象被緩存。對對象進行的任何修改都將反映在指向該 Bean 的所有引用中。此作用域是如果沒有指定其他作用域時默認值。

讓我們創建一個 Person 實體來闡明作用域的概念:

public class Person {
    private String name;

    // standard constructor, getters and setters
}

之後,我們使用 singleton 作用域,通過使用 @Scope 註解定義 Bean:

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

我們可以使用常量代替 String 值,如下所示:

@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)

現在我們可以繼續編寫一個測試,以證明兩個引用同一個 Bean 的對象即使只有一個改變其狀態,它們仍然會具有相同的數值,因為它們都引用同一個 Bean 實例:

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();
}

<p>本示例中的 <em >scopes.xml</em> 文件應包含所使用的 Bean 的 XML 定義。</p>

<?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)

具有prototype樣貌的 Bean 在從容器中請求時,每次都會返回不同的實例。它通過將prototype的值設置為@Scope註解在 Bean 定義中來定義:

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

我們還可以使用常量,就像我們為單例範圍所做的那樣:

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)

我們現在將編寫一個與之前類似的測試,該測試展示了兩個對象同時請求同一個 Bean 名稱,並且使用了 prototype 作用域。由於它們不再引用同一個 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();
}

<em>scopes.xml</em> 文件與上一節中提供的類似,但同時添加了具有 <em>prototype</em> 作用域的 Bean 的 XML 定義:

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

4. 網頁感知範圍

如前所述,只有在網頁感知應用程序上下文中才可用的有四個額外範圍。我們在實踐中較少使用這些範圍。

request範圍為單個HTTP請求創建一個 Bean 實例,而session範圍為HTTP會話創建一個 Bean 實例。

application範圍為ServletContext的生命週期創建一個 Bean 實例,websocket範圍則為特定的WebSocket會話創建一個 Bean 實例。

讓我們創建一個用於實例化 Bean 的類:

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

4.1. 請求作用域

我們可以使用 @Scope 註解定義具有 請求 作用域的 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 作用域的 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 字段的值保持一致。

在這種情況下,第一次請求時,message 字段的值為 null。但是,一旦被修改後,該值將保留在後續的請求中,因為整個會話中返回的是同一個 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. 應用範圍

<em>應用範圍</em> 創建 Servlet 域生命週期中的 Bean 實例。

這與 <em>單例</em> 範圍類似,但與 Bean 的範圍存在一個非常重要的區別。

當 Bean 被設置為 <em>應用範圍</em> 時,相同的 Bean 實例將共享在同一 Servlet 域中運行的多個基於 Servlet 的應用程序之間,而 <em>單例</em> 範圍的 Bean 僅限於單個應用程序上下文。

讓我們使用 <em>應用範圍</em> 創建 Bean:

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

requestsession 作用域類似,我們也可以使用更短的版本:

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

在這種情況,一旦在 <em >applicationScopedBean</em > 中設置,<em >message</em >> 的值將保留在所有後續請求、會話以及即使是訪問該 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 會話屬性中。同一 Bean 實例會在整個 WebSocket 會話期間,每次訪問時返回。

我們也可以説它表現出單例行為,但僅限於單個 WebSocket 會話。

結論

在本文中,我們討論了 Spring 提供的不同 Bean 作用域及其用途。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.