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();
}與 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";
}
}在這種情況,一旦在 <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 作用域及其用途。