【Spring】原型 Bean 被固定

問題描述

  • 在定義 Bean 時,有時候我們會使用原型 Bean,例如定義如下:

    @Service
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public class ServiceImpl {
    }
    
  • 然后我們按照下面的方式去使用它:

    @RestController
    public class HelloWorldController {@Autowiredprivate ServiceImpl serviceImpl;@RequestMapping(path = "hi", method = RequestMethod.GET)public String hi(){return "helloworld, service is : " + serviceImpl;};
    }
    
  • 結果,我們會發現,不管我們訪問多少次http://localhost:8080/hi,訪問的結果都是不變的,如下:

    helloworld, service is : com.spring.puzzle.class1.example3.error.ServiceImpl@4908af

  • 很明顯,這很可能和我們定義 ServiceImpl 為原型 Bean 的初衷背道而馳,如何理解這個現象呢?

案例分析

  • 當一個屬性成員 serviceImpl 聲明為 @Autowired 后,那么在創建 HelloWorldController 這個 Bean 時,會先使用構造器反射出實例,然后來裝配各個標記為 @Autowired 的屬性成員(裝配方法參考 AbstractAutowireCapableBeanFactory#populateBean)。

  • 具體到執行過程,它會使用很多 BeanPostProcessor 來做完成工作,其中一種是 AutowiredAnnotationBeanPostProcessor,它會通過 DefaultListableBeanFactory#findAutowireCandidates 尋找到 ServiceImpl 類型的 Bean,然后設置給對應的屬性(即 serviceImpl 成員)。

  • 關鍵執行步驟可參考 AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject:

    protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Field field = (Field) this.member;Object value;//尋找“bean”if (this.cached) {value = resolvedCachedArgument(beanName, this.cachedFieldValue);}else {//省略其他非關鍵代碼value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);}if (value != null) {//將bean設置給成員字段ReflectionUtils.makeAccessible(field);field.set(bean, value);}
    }
    
  • 待我們尋找到要自動注入的 Bean 后,即可通過反射設置給對應的 field。這個 field 的執行只發生了一次,所以后續就固定起來了,它并不會因為 ServiceImpl 標記了 SCOPE_PROTOTYPE 而改變。

  • 所以,當一個單例的 Bean,使用 autowired 注解標記其屬性時,你一定要注意這個屬性值會被固定下來。

問題修正

  • 通過上述源碼分析,我們可以知道要修正這個問題,肯定是不能將 ServiceImpl 的 Bean 固定到屬性上的,而應該是每次使用時都會重新獲取一次。所以這里我提供了兩種修正方式:
1. 自動注入 Context
  • 即自動注入 ApplicationContext,然后定義 getServiceImpl() 方法,在方法中獲取一個新的 ServiceImpl 類型實例。修正代碼如下:

    @RestController
    public class HelloWorldController {@Autowiredprivate ApplicationContext applicationContext;@RequestMapping(path = "hi", method = RequestMethod.GET)public String hi(){return "helloworld, service is : " + getServiceImpl();};public ServiceImpl getServiceImpl(){return applicationContext.getBean(ServiceImpl.class);}}
    
2. 使用 Lookup 注解
  • 類似修正方法 1,也添加一個 getServiceImpl 方法,不過這個方法是被 Lookup 標記的。修正代碼如下:

    @RestController
    public class HelloWorldController {@RequestMapping(path = "hi", method = RequestMethod.GET)public String hi(){return "helloworld, service is : " + getServiceImpl();};@Lookuppublic ServiceImpl getServiceImpl(){return null;}  }
    
    • 通過這兩種修正方式,再次測試程序,我們會發現結果已經符合預期(每次訪問這個接口,都會創建新的 Bean)。
  • 這里我們不妨再拓展下,討論下 Lookup 是如何生效的。畢竟在修正代碼中,我們看到 getServiceImpl 方法的實現返回值是 null,這或許很難說服自己。

  • 首先,我們可以通過調試方式看下方法的執行,參考下圖

在這里插入圖片描述

  • 從上圖我們可以看出,我們最終的執行因為標記了 Lookup 而走入了 CglibSubclassingInstantiationStrategy.LookupOverrideMethodInterceptor,這個方法的關鍵實現參考 LookupOverrideMethodInterceptor#intercept:

    private final BeanFactory owner;public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);Assert.state(lo != null, "LookupOverride not found");Object[] argsToUse = (args.length > 0 ? args : null);  // if no-arg, don't insist on args at allif (StringUtils.hasText(lo.getBeanName())) {return (argsToUse != null ? this.owner.getBean(lo.getBeanName(), argsToUse) :this.owner.getBean(lo.getBeanName()));}else {return (argsToUse != null ? this.owner.getBean(method.getReturnType(), argsToUse) :this.owner.getBean(method.getReturnType()));}
    }
    
  • 我們的方法調用最終并沒有走入案例代碼實現的 return null 語句,而是通過 BeanFactory 來獲取 Bean。所以從這點也可以看出,其實在我們的 getServiceImpl 方法實現中,隨便怎么寫都行,這不太重要。

  • 例如,我們可以使用下面的實現來測試下這個結論:

    @Lookup
    public ServiceImpl getServiceImpl(){//下面的日志會輸出么?log.info("executing this method");return null;
    }  
    
  • 以上代碼,添加了一行代碼輸出日志。測試后,我們會發現并沒有日志輸出。這也驗證了,當使用 Lookup 注解一個方法時,這個方法的具體實現已并不重要。

  • 再回溯下前面的分析,為什么我們走入了 CGLIB 搞出的類,這是因為我們有方法標記了 Lookup。我們可以從下面的這段代碼得到驗證,參考 SimpleInstantiationStrategy#instantiate:

    @Override
    public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {// Don't override the class with CGLIB if no overrides.if (!bd.hasMethodOverrides()) {//return BeanUtils.instantiateClass(constructorToUse);}else {// Must generate CGLIB subclass.return instantiateWithMethodInjection(bd, beanName, owner);}
    }
    
  • 在上述代碼中,當 hasMethodOverrides 為 true 時,則使用 CGLIB。而在本案例中,這個條件的成立在于解析 HelloWorldController 這個 Bean 時,我們會發現有方法標記了 Lookup,此時就會添加相應方法到屬性 methodOverrides 里面去(此過程由 AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors 完成)。

  • 添加后效果圖如下:
    在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/66545.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/66545.shtml
英文地址,請注明出處:http://en.pswp.cn/web/66545.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

2024年美賽C題評委文章及O獎論文解讀 | AI工具如何影響數學建模?從評委和O獎論文出發-O獎論文做對了什么?

模型假設僅僅是簡單陳述嗎?允許AI的使用是否降低了比賽難度?還在依賴機器學習的模型嗎?處理題目的方法有哪些?O獎論文的優點在哪里? 本文調研了當年賽題的評委文章和O獎論文,這些問題都會在文章中一一解答…

PyTorch框架——基于深度學習YOLOv8神經網絡學生課堂行為檢測識別系統

基于YOLOv8深度學習的學生課堂行為檢測識別系統,其能識別三種學生課堂行為:names: [舉手, 讀書, 寫字] 具體圖片見如下: 第一步:YOLOv8介紹 YOLOv8 是 ultralytics 公司在 2023 年 1月 10 號開源的 YOLOv5 的下一個重大更新版本…

kafka學習筆記6 ACL權限 —— 筑夢之路

在Kafka中,ACL(Access Control List)是用來控制誰可以訪問Kafka資源(如主題、消費者組等)的權限機制。ACL配置基于Kafka的kafka-acls.sh工具,能夠管理對資源的讀取、寫入等操作權限。 ACL介紹 Kafka的ACL是…

探秘自然地理:從太陽到地球的奇妙之旅與災害預警

在浩瀚無垠的宇宙中,我們的地球與太陽緊密相連,它們的奧秘和變化,時刻影響著我們的生活。今天,就讓我們一同深入探索自然地理的基礎知識,揭開太陽與地球的神秘面紗,同時了解那些可能給我們帶來巨大影響的自…

領域算法 - 負載均衡算法

負載均衡算法 文章目錄 負載均衡算法一:常規負載均衡算法二:Nginx負載均衡算法 一:常規負載均衡算法 二:Nginx負載均衡算法 # 定義負載均衡設備的Ip及設備狀態 upstream bakend {ip_hash; server 127.0.0.1:9090 down; server…

Access數據庫教案(Excel+VBA+Access數據庫SQL Server編程)

文章目錄: 一:Access基礎知識 1.前言 1.1 基本流程 1.2 基本概念?? 2.使用步驟方法 2.1 表【設計】 2.1.1 表的理論基礎 2.1.2 Access建庫建表? 2.1.3 表的基本操作 2.2 SQL語句代碼【設計】 2.3 窗體【交互】? 2.3.1 多方式創建窗體 2.3.2 窗體常用的控件 …

圖片生成Prompt編寫技巧

1. 圖片情緒(場景氛圍) 一張圖片一般都會有一個情緒基調,因為作畫本質上也是在傳達一些情緒,一般都會借助圖片的氛圍去轉達。例如:比如家庭聚會一般是歡樂、喜樂融融。斷壁殘垣一般是悲涼。還有蕭瑟、孤寂等。 2.補充細…

算法題目總結-鏈表

文章目錄 1.環形鏈表1.答案2.思路 2.兩數相加1.答案2.結果 3.反轉鏈表1.答案2.思路 4.反轉鏈表 II1.答案2.思路 5.K 個一組翻轉鏈表1.答案2.思路 6.刪除鏈表的倒數第 N 個結點1.答案2.思路 7.刪除排序鏈表中的重復元素 II1.答案2.思路 8.旋轉鏈表1.答案2.思路 9.LRU 緩存1.答案…

【Unity3D】3D物體擺放、場景優化案例Demo

目錄 PlaceManager.cs(放置管理類) Ground.cs(地板類) 和 GroundData.cs(地板數據類) 額外知識點說明 1、MeshFilter和MeshRenderer的Bounds區別 2、Gizmos 繪制一個平行于斜面的立方體 通過網盤分享的文件:PlaceGameDemo2.unitypackage 鏈接: https://pan.baid…

OpenEuler學習感悟

在初次接觸 OpenEuler 時,我深感其學習難度較大。它與我之前熟悉的操作系統存在諸多差異,學習過程中,需要理解復雜的內核機制、掌握獨特的系統配置方法。但正是這種挑戰,激發了我深入探索的熱情。 從理論學習入手,我發…

C# OpenCvSharp 部署文檔矯正,包括文檔扭曲/模糊/陰影等情況

目錄 說明 效果 模型 項目 代碼 下載 參考 C# OpenCvSharp 部署文檔矯正,包括文檔扭曲/模糊/陰影等情況 說明 地址:https://github.com/RapidAI/RapidUnDistort 修正文檔扭曲/模糊/陰影等情況,使用onnx模型簡單輕量部署&#xff0c…

CSS 溢出問題及解決方案:實用案例與技巧

在網頁開發中,CSS 的布局和樣式起著至關重要的作用,但經常會遇到一個棘手的問題——溢出問題。溢出是指元素內的內容超出了其設定的容器大小,這不僅會影響頁面的美觀,還可能干擾用戶體驗。本文將詳細探討 CSS 溢出問題的案例&…

生成樹機制實驗

1 實驗內容 1、基于已有代碼,實現生成樹運行機制,對于給定拓撲(four_node_ring.py),計算輸出相應狀態下的生成樹拓撲 2、構造一個不少于7個節點,冗余鏈路不少于2條的拓撲,節點和端口的命名規則可參考four_node_ring.py,使用stp程序計算輸出生成樹拓撲 2 實驗原理 一、…

數據結構詳解——堆與二叉樹

? 目錄 樹的概念樹的表示方法二叉樹的概念特殊的二叉樹二叉樹的性質二叉樹的存儲結構順序存儲鏈式存儲 堆的概念與結構堆的性質堆的實現堆的初始化入堆堆的擴容向上調整算法出堆(最頂端元素)向下調整算法 二叉樹的實現二叉樹的創建二叉樹的銷毀二叉樹的…

【藍橋杯】43694.正則問題

題目描述 考慮一種簡單的正則表達式: 只由 x ( ) | 組成的正則表達式。 小明想求出這個正則表達式能接受的最長字符串的長度。 例如 ((xx|xxx)x|(x|xx))xx 能接受的最長字符串是: xxxxxx,長度是 6。 輸入描述 一個由 x()| 組成的正則表達式。…

mac m1下載maven安裝并配置環境變量

下載地址:Download Apache Maven – Maven 解壓到一個沒有中文和空格的文件夾 輸入pwd查看安裝路徑 輸入cd返回根目錄再輸入 code .zshrc 若顯示 command not found: code你可以通過以下步驟來安裝和配置 code 命令: 1. 確保你已經安裝了 Visual Studio…

【自己動手開發Webpack插件:開啟前端構建工具的個性化定制之旅】

在前端開發的世界里,Webpack無疑是構建工具中的“明星”。它強大的功能可以幫助我們高效地打包和管理前端資源。然而,有時候默認的Webpack功能可能無法完全滿足我們的特定需求,這時候就需要自定義Webpack插件來大展身手啦!今天&am…

移遠通信多模衛星通信模組BG95-S5獲得Skylo網絡認證,進一步拓展全球衛星物聯網市場

近日,全球領先的物聯網整體解決方案供應商移遠通信正式宣布,其支持“衛星蜂窩”多模式的高集成度NTN衛星通信模組BG95-S5已成功獲得NTN網絡運營商Skylo的網絡認證。BG95-S5也成為了獲得該認證的最新款移遠衛星通信模組。 BG95-S5模組順利獲得Skylo認證&a…

1.3.淺層神經網絡

目錄 1.3.淺層神經網絡 1.3.1 淺層神經網絡表示 1.3.2 單個樣本的向量化表示 1.3.4 激活函數的選擇 1.3.5 修改激活函數 1.3.5 練習??????? 1.3.淺層神經網絡 1.3.1 淺層神經網絡表示 之前已經說過神經網絡的結構了,在這不重復敘述。假設我們有如下…

StarRocks強大的實時數據分析

代碼倉庫:https://github.com/StarRocks/starrocks?tabreadme-ov-file StarRocks | A High-Performance Analytical Database 快速開始:StarRocks | StarRocks StarRocks 是一款高性能分析型數據倉庫,使用向量化、MPP 架構、CBO、智能物化…