Bean的作用域

来源:https://www.cnblogs.com/evanlin/archive/2022/04/10/16127119.html
-Advertisement-
Play Games

Spring容器創建的Bean預設是單例的。Spring容器調用配置方法完成Bean的創建之後,Bean就緩存在Spring容器里。之後每次調用同一配置方法創建Bean,Spring容器只會返回緩存在Spring容器里的Bean,不再創建新的Bean。這意味著同一配置方法在同一Spring容器里無論 ...


Spring容器創建的Bean預設是單例的。Spring容器調用配置方法完成Bean的創建之後,Bean就緩存在Spring容器里。之後每次調用同一配置方法創建Bean,Spring容器只會返回緩存在Spring容器里的Bean,不再創建新的Bean。這意味著同一配置方法在同一Spring容器里無論被調用了多少次,都只會返回同一實例的Bean。因此,Spring容器創建的Bean預設是單例的。同時我們也應註意到,這裡的單例與單例設計模式里的單例是有區別的,不能混為一談。單例設計模式里的單例指的是類的實例由類的載入器方法創建,無論類的載入器方法被調用了多少次,都只會返回同一實例。

在Web開發中,我們通常只需創建單例的Bean。因為諸如控制器之類的Bean是無狀態的。無論哪個用戶發來請求,都能使用同一控制器實例處理,根本就不需要再創建新的控制器實例。然而對於一些類,比如數據模型類,每個請求所產生或獲取的數據都是不一樣的。這意味著這樣的類是有狀態的。把這些有狀態的類創建為單例的顯然不妥。作為替代,我們通常選擇創建這些類的域對象(Domain Object),通過new關鍵字在Bean的方法中創建這些類的實例。因此,在Web開發中,我們往往只需告訴Spring容器創建單例的Bean

然而,在某些罕見的應用場景中,我們可能需要創建非單例的Bean。這意味著除了單例(Singleton)作用域之外,Spring容器還需支持創建具有其它作用域的Bean。具體如下:
1.原型(Prototype):Spring容器每次調用配置方法創建Bean時都會重新創建Bean的實例,調用幾次就創建幾個實例。
2.請求(Request):請求指的是Web請求,只有Web相關的Spring容器(比如XmlWebApplicationContext)才支持請求作用域。指定作用域為請求後,同一配置方法在同一Web請求里無論被調用了多少次,都只會創建一個Bean的實例。
3.會話(Session):會話指的是Web會話,只有Web相關的Spring容器(比如XmlWebApplicationContext)才支持會話作用域。指定作用域為會話後,同一配置方法在同一Web會話里無論被調用了多少次,都只會創建一個Bean的實例。

Bean的作用域可由@Scope註解配置。@Scope註解有個String類型的value屬性。我們可把singleton(單例),prototype(原型),request(請求)或session(會話)這些字元串指給@Scope註解,告訴Spring容器創建具有相應作用域的Bean。如下所示:

1 @Bean("music")
2 @Scope(value="singleton")
3 public Music produceMusic() {
4     return new Music("Dream");
5 }

當然,@Scope註解除了可以加到配置方法之外,也能以同樣的方式加到帶有@Component註解的組件上。至於XML配置文件,則可使用XML的scope屬性這樣配置:

1 <bean id="music" class="com.dream.Music" scope="singleton">
2     <constructor-arg value="Dream" />
3 </bean>

於是,我們弄清楚了單例,原型,請求,會話這些作用域。卻也開始感到困惑:“假如把原型作用域的Bean註入到單例作用域的Bean中,這時會怎麼樣?”

毫無疑問,這是一個問題。Spring容器創建單例的Bean時就把原型的Bean註入進去了。之後,Spring容器每次用到單例的Bean時都是從Spring容器那裡獲取的,沒再創建新的實例。這意味著原型的Bean只在註入的時候創建了一次,之後一直被單例的Bean引用著,無論單例的Bean用了多少次原型的Bean,原型的Bean始終是註入時的那個實例。如果我們希望單例的Bean每次用到原型的Bean時,原型的Bean都會返回一個新的實例,則需要做些額外的配置。而這配置,其中之一就是查找方法註入(Lookup Method Injection)

簡單來說,查找方法註入就是單例的Bean每次用到原型的Bean時,都會調用指定的方法從Spring容器那裡獲取原型的Bean。而從Spring容器那裡獲取原型的Bean時,Spring容器總會返回新的實例。如此一來,單例的Bean每次用到原型的Bean時,原型的Bean的實例就總是新的了。

假如現有這樣一個原型作用域的Bean:

 1 @Scope("prototype")
 2 @Component("music")
 3 public class Music {
 4     private String musicName = null;
 5 
 6     public Music(@Value("Dream") String musicName) {
 7         this.musicName = musicName;
 8     }
 9 
10     // 省略getter, setter方法
11 }

我們希望把它註入到單例作用域的Bean里。這時可以這樣定義單例作用域的Bean:

1 @Component("player")
2 public abstract class Player {
3     @Lookup(value="music")
4     protected abstract Music getPlayingMusic();
5 
6     // 省略其它代碼
7 }

這是一個抽象類,定義了一個抽象方法,用於獲取Music類型的Bean。特別引人註目的是,抽象方法上面帶著一個神秘的@Lookup(value="music")註解。

這是怎麼回事呢?

原來,Spring容器瞧見@Lookup註解之後就會生成一個代理類。代理類將重寫帶有@Lookup註解的抽象方法,使之具有這樣的功能:從Spring容器那裡查找@Lookup註解指定的Bean,併在找到之後進行返回。這樣一來,單例的Bean每次用到原型的Bean時,都會調用代理方法從Spring容器那裡獲取原型的Bean。而從Spring容器那裡獲取的原型的Bean的實例總是新的,從而使單例的Bean每次用到原型的Bean時,用的都是新的實例。

因此,@Lookup註解有個String類型的value屬性,用於指定即將查找的Bean的ID。如果沒有指定value屬性,代理方法就會查找與代理方法的返回值的類型一樣的Bean

在我們的配置中,我們在抽象方法getPlayingMusic上添加了@Lookup(value="music")註解,告訴Spring容器生成代理類,使單例的Player每次用到的原型的Music都是新的實例。

還有,XML也支持同樣的配置。具體如下:

1 <beans  /* 省略命名空間和XSD模式文件聲明 */>
2     <bean id="music" class="com.dream.Music" scope="prototype">
3         <constructor-arg value="Dream" />
4     </bean>
5 
6     <bean id="player" class="com.dream.Player">
7         <lookup-method name="getPlayingMusic" bean="music" />
8     </bean>
9 </beans>

這段代碼使用XML配置了兩個Bean:

1.一個Bean是Music類型的,其作用域是原型的。
2.一個Bean是Player類型的,其作用域沒有指定,預設是單例的。

特別需要留意的是,配置Player類型的Bean時用到了 <lookup-method name="getPlayingMusic" bean="music" /> 元素。Spring容器瞧見這個元素之後,就會生成一個代理類。代理類將重寫<lookup>元素的name屬性指定的方法,使之每次被調用的時候,都從Spring容器那裡獲取<lookup>元素的bean屬性指定的Bean。如此一來,單例的Player每次用到原型的Music時,用的就都是新的實例了。

於是,我們弄清楚了怎樣把原型的Bean註入單例的Bean里,可這並不意味著我們可以停下探索的腳步。因為把請求作用域的Bean註入單例作用域的Bean里也有同樣的問題。

假如現有這樣一個單例作用域的Bean:

1 @Component
2 public class Player {
3     private Music playingMusic = null;
4 
5     @Autowired
6     public Player(Music playingMusic) {
7         this.playingMusic = playingMusic;
8     }
9 }

我們希望註入Player構造函數的是一個請求作用域的Music類型的Bean。這時可以這樣配置Music:

 1 @Component
 2 @Scope(value="request", proxyMode = ScopedProxyMode.TARGET_CLASS)
 3 public class Music {
 4     private String musicName = null;
 5 
 6     public String getMusicName() {
 7         return this.musicName;
 8     }
 9 
10     @Value("Dream")
11     public void setMusicName(String musicName) {
12         this.musicName = musicName;
13     }
14 }

Music類上帶著@Scope(value="request", proxyMode = ScopedProxyMode.TARGET_CLASS)註解,其value屬性的值是 request ,表明該Bean的作用域是請求。同時我們也註意到了,@Scope註解還有一個proxyMode屬性,其值是ScopedProxyMode.TARGET_CLASS

這是怎麼回事呢?

原來,proxyMode屬性是ScopedProxyMode枚舉類型的,能夠告訴Spring容器生成代理的方式。具體如下:
1.NO:告訴Spring容器無需生成代理。
2.TARGET_CLASS:告訴Spring容器基於類生成代理。
3.INTERFACES:告訴Spring容器基於介面生成代理。
4.DEFAULT:預設的代理方式。預設與NO一樣,用於告訴Spring容器無需生成代理。也可通過配置,使之告訴Spring容器預設基於類或介面生成代理。

由此可知,如果把ScopedProxyMode.TARGET_CLASS或ScopedProxyMode.INTERFACES指給proxyMode屬性,Spring容器就會生成代理類。Spring容器創建Bean的時候,只會創建代理類的Bean。因此,Spring容器把請求作用域的Bean註入到單例作用域的Bean時,註入的實際是代理類的Bean。如此一來,單例的Bean用到請求的Bean時,用的實際是代理類的Bean。代理類的Bean會先判斷一下當前是不是在同一Web請求里:如果是,則返回緩存在Spring容器里的Bean;如果不是,則再創建一個新的實例進行返回。從而使單例的Bean用到請求的Bean時,不同的Web請求將會返回Bean的不同實例。

Spring容器生成代理的方式有兩種:一種是基於類生成代理;一種是基於介面生成代理。如果希望基於類生成代理,可把@Scope註解的proxyMode屬性的值置為ScopedProxyMode.TARGET_CLASS。Music類上的@Scope註解的proxyMode屬性的值就是ScopedProxyMode.TARGET_CLASS;如果希望基於介面生成代理,則必須讓我們的類實現某個介面。因此,配置之前我們首先需要定義一個介面:

1 public interface IMusic {
2     public String getMusicName();
3     public void setMusicName(String musicName);
4 }

之後讓Music類實現IMusic介面,並把proxyMode屬性的值置為ScopedProxyMode.INTERFACES:

 1 @Component
 2 @Scope(value="request", proxyMode = ScopedProxyMode.INTERFACES)
 3 public class Music implements IMusic {
 4     private String musicName = null;
 5 
 6     @Override
 7     public String getMusicName() {
 8         return this.musicName;
 9     }
10 
11     @Override
12     @Value("Dream")
13     public void setMusicName(String musicName) {
14         this.musicName = musicName;
15     }
16 }

最後把註入Player的Music改成IMusic介面:

 1 @Component
 2 public class Player {
 3     private IMusic playingMusic = null;
 4 
 5     public IMusic getPlayingMusic() {
 6         return this.playingMusic;
 7     }
 8 
 9     @Autowired
10     public Player(IMusic playingMusic) {
11         this.playingMusic = playingMusic;
12     }
13 }

於是,基於介面生成代理的配置就完成了。當然,這裡只講了怎樣進行請求作用域的註入。可實際上,會話作用域也有同樣的問題。我們只需進行同樣的配置就行了,不再贅敘。還有,如果想用XML進行同樣的配置,則可提供一個XML配置文件配置如下:

 1 <beans /* 省略命名空間和XSD模式文件聲明 */
 2        xmlns:aop="http://www.springframework.org/schema/aop"
 3        xsi:schemaLocation="
 4        /* 省略命名空間和XSD模式文件聲明 */
 5        http://www.springframework.org/schema/aop
 6        http://www.springframework.org/schema/aop/spring-aop.xsd">
 7 
 8     <bean id="music" class="com.dream.Music" scope="request">
 9         <aop:scoped-proxy proxy-target-class="false" />
10         <property name="musicName" value="Dream" />
11     </bean>
12 
13     <bean id="player" class="com.dream.Player">
14         <constructor-arg ref="music" />
15     </bean>
16 </beans>

這段配置引入了spring-aop.xsd模式文件。這是一個用於配置面向切麵編程的模式文件,我們將在介紹面向切麵編程的時候另行介紹。現在只需知道這個模式文件定義了個<aop:scoped-proxy>元素,用於配置作用域代理。裡面有個proxy-target-class屬性,用於配置生成代理的方式:如果proxy-target-class屬性的值是TRUE,則基於類生成代理;如果proxy-target-class屬性的值是FALSE,則基於介面生成代理。proxy-target-class屬性的值預設是TRUE

至此,關於Bean的作用域的介紹也就告一段落了。下章,我們將會開始介紹事件的監聽與發佈。歡迎大家繼續閱讀,謝謝大家!

返回目錄    下載代碼


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Excelize 是 Go 語言編寫的用於操作 Office Excel 文檔基礎庫。2022年4月11日,社區正式發佈了 2.6.0 版本,該版本包含了多項新增功能、錯誤修複和相容性提升優化。下麵是有關該版本更新內容的摘要。 ...
  • 快速上手JWT簽發token和認證,有這一篇就夠了,DRF自帶的和自定義的都幫你總結好了,拿去用~ ...
  • Django REST framework JWT 在用戶註冊或登錄後,我們想記錄用戶的登錄狀態,或者為用戶創建身份認證的憑證。我們不再使用Session認證機制,而使用Json Web Token認證機制。 Json web token (JWT), 是為了在網路應用環境間傳遞聲明而執行的一種基於 ...
  • 目錄 一.簡介 二.效果演示 三.源碼下載 二.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 O ...
  • 需求:將一個寫著班級內學生姓名的文件(每個名字占一行),進行隨機點名 分析: 1、創建字元緩衝輸入流對象 2、創建ArrayList集合對象 3、調用字元緩衝輸入流對象方法進行讀取數據 4、將讀到的數據存儲在集合中 5、釋放資源 6、生成一個隨機數,範圍在0-集合.size 7、通過產生的隨機數當作 ...
  • 列表也非常適合存儲一組數字,尤其是大數據處理,處理的幾乎都是由數字(如氣溫、距離、人口數量、經濟等)組成的集合。 Python提供很多工具,在數據可視化中,可高效地處理數字列表。 一、數值列表 range() 讓你能夠輕鬆地生成一系列的數字 for value in range(1,6): prin ...
  • 有些時候,我們希望某件事情發生的時候能夠觸發一個事件,讓這個事件幫我們做些事情。比如,在晚上十一點到晚上十二點這段時間,假如還有人在使用我們的軟體,我們就觸發一個事件播放一首美妙的音樂,希望用戶聽了之後能夠做個好夢,美美睡上一覺。而這,我們需要做好三件事情: 1.定義一個事件。 2.定義一個事件監聽 ...
  • HiKariCP作為SpringBoot2框架的預設連接池,號稱是跑的最快的連接池,資料庫連接池與之前兩篇提到的線程池和對象池,從設計的原理上都是基於池化思想,只是在實現方式上有各自的特點; ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...