關(guān)于spring中單例Bean引用原型Bean產(chǎn)生的問題及解決
spring單例Bean引用原型Bean產(chǎn)生的問題及解決
問題描述
spring里Bean都有一個作用域,用注解@Scope表示,容器默認(rèn)使用的是單例,即“singleton”顧名思義就是指容器每次只為我們創(chuàng)建一次Bean,以后使用都是拿的同一個。
雖然平時開發(fā)的時候基本都是使用單例的,但不免有時候會使用到另一種類型,即原型模式,這時候就會產(chǎn)生一個問題:當(dāng)我們單例Bean注入了原型Bean,并且調(diào)用了原型Bean的某個方法,我們希望的是單例Bean只初始化一次,而原型Bean每次調(diào)用應(yīng)該都是重新生成的,然而,結(jié)果確與我們想的并不一樣,無論是單例還是原型都只會初始化一次。
為了更直觀的發(fā)現(xiàn)問題,下面我們用代碼演示一遍
首先新建一個SpringConfig配置類:
@Configuration @ComponentScan("com.whb") public class AppConfig { }
這個只是一個配置類,并且指定了容器掃描“com.whb”下面的類。
接著我們在com.whb下新建2個類:
@Component("dao") @Scope("prototype") public class IndexDao { }
@Component public ?class IndexService ?{ ? ? @Autowired ? ? IndexDao indexDao; ? ? public void test() { ? ? ? ? System.out.println("service:"+this.hashCode()); ? ? ? ? System.out.println("dao:"+indexDao.hashCode()); ? ? } }
IndexDao 指定原型模式,而IndexService則是默認(rèn)的單例模式,我們往IndexSerevice里注入IndexDao,并且在test方法里打印出2個類的哈希值,來驗(yàn)證是否一致。
新建一個Test來測試
public class Test { ? ? public static void main(String[] args) { ? ? ? ? AnnotationConfigApplicationContext an = new AnnotationConfigApplicationContext(AppConfig.class); ? ? ? ? IndexService indexService = an.getBean(IndexService.class); ? ? ? ? indexService.test(); ? ? ? ? IndexService indexService1 = an.getBean(IndexService.class); ? ? ? ? indexService1.test(); ? ? ? ? IndexService indexService2 = an.getBean(IndexService.class); ? ? ? ? indexService2.test(); ? ? } }
這里我們直接從容器中取3次IndexService ,同時調(diào)用3次test()方法,結(jié)果如下:
問題來了,IndexService是單例哈希值不變可以理解,那為什么設(shè)置了原型的IndexDao也不變呢?
問題分析
其實(shí)仔細(xì)想一下,這問題很好理解。當(dāng)我們第一次從容器中拿IndexService時,他會為我們創(chuàng)建一個實(shí)例,創(chuàng)建過程中發(fā)現(xiàn)IndexService還依賴著另一個屬性,那么此時容器便會先去生成IndexDao的實(shí)例并且注入到IndexService中,然后創(chuàng)建出IndexService實(shí)例并且返回。
此時容器中有著一個IndexService和一個IndexDao。當(dāng)我們第二次去拿IndexService時,容器發(fā)現(xiàn)已經(jīng)有了一個實(shí)例,并且IndexService是單例的所有它直接就返回了那個存在著的IndexService實(shí)例。雖然IndexDao設(shè)置了原型,但由于IndexService只有一次機(jī)會設(shè)置屬性所以從到尾容器并沒有生成第二個IndexDao實(shí)例。這也就解釋了為什么哈希值每次都是一樣的。
真實(shí)情況下我們肯定不希望是這個結(jié)果,不然的話我們設(shè)置原型還有個毛線作用。
其實(shí)spring官方文檔給出了2中解決方案,點(diǎn)擊我查看文檔說明
下面我直接貼出代碼演示一遍:
方法一:實(shí)現(xiàn)ApplicationContextAware
我們來修改下IndexService的內(nèi)容:
public class IndexService implements ApplicationContextAware{ private ApplicationContext applicationContext; public void print() { System.out.println("service:"+this.hashCode()); System.out.println("dao:"+applicationContext.getBean("dao").hashCode()); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
我們通過實(shí)現(xiàn)ApplicationContextAware這個接口并重寫setApplicationContext()這個方法可以拿到ApplicationContext對象,從而用這個對象獲取容器中的Bean.
結(jié)果如下:
可以看到service還是每次都一樣,符合單例設(shè)置,但是dao確每次都改變了,說明原型設(shè)置也生效了。
上面雖然解決了這個問題,但是這種方法還是有一定的弊端:
ApplicationContextAware是spring提供給我們的接口,IndexService是我們業(yè)務(wù)的類,我們直接實(shí)現(xiàn)可以說是增大了和spring框架的耦合程度。因此spring還提供了第二種方法:
方法二:使用@LookUp注解
我們繼續(xù)修改IndexService的內(nèi)容:
@Component public abstract class IndexService { public void print() { System.out.println("service:"+this.hashCode()); System.out.println("dao:"+getIndexDao().hashCode()); } @Lookup("dao") public abstract IndexDao getIndexDao(); }
可以看到使用這種方式代碼簡潔了很多,我們只需要聲明一個抽象方法,并在該方法上面添加@Lookup(“dao”)注解,“dao”表示要注入的類名,spring容器就會自動幫我們注入IndexDao實(shí)例。
結(jié)果如下:
總結(jié):在spring開發(fā)中可能會遇到各種各樣的問題,但其實(shí)最好的解決辦法就是查閱文檔,畢竟這可是第一手資料?。?/p>
spring Bean的幾個相關(guān)問題
1.Spring Bean 作用域
Spring 容器中的 bean 可以分為 5 個范圍(scope配置項(xiàng))。
1)singleton(單例模式) :這個模式是默認(rèn)的,使用該屬性定義Bean時,IOC容器僅創(chuàng)建一個Bean實(shí)例,IOC容器每次返回的是同一個Bean實(shí)例。
2)prototype(原型模式) :使用該屬性定義Bean時,IOC容器可以創(chuàng)建多個Bean實(shí)例,每次返回的都是一個新的實(shí)例。
3)request(HTTP請求) :該屬性僅對HTTP請求產(chǎn)生作用,每次HTTP請求都會創(chuàng)建一個新的Bean,在請求完成以后,bean 會失效并被垃圾回收器回收。
4)Session(會話) :該屬性僅用于HTTP Session,同一個Session共享一個Bean實(shí)例。不同Session使用不同的實(shí)例。在 session 過期后,bean 會隨之失效。
5)global-session(全局會話) :該屬性僅用于HTTP Session,所有的Session共享一個Bean實(shí)例。
2.什么是Spring inner beans
將這一個bean聲明為另一個Bean的內(nèi)部bean。內(nèi)部bean可以用setter注入“屬性”和構(gòu)造方法注入“構(gòu)造參數(shù)”的方式來實(shí)現(xiàn)。無論何時此內(nèi)部bean被使用,僅僅作為被作為一個屬性被調(diào)用。
3.什么是有狀態(tài)、無狀態(tài)
- 單例:某個類系統(tǒng)范圍內(nèi)只有一個實(shí)例
- 多例:某個類在系統(tǒng)范圍內(nèi)同時有多個實(shí)例
- 無狀態(tài)類:類中沒有狀態(tài)信息,一般是無成員變量或成員變量的值是不變的。
- 有狀態(tài)類:類中有狀態(tài)信息,一般表現(xiàn)成員變量的值可變,在某一時該被調(diào)用而改變狀態(tài),之后再調(diào)用時獲取其正確的狀態(tài)。
4.Spring框架中的單例Bean是線程安全的么
單例模式確保某一個類只有一個實(shí)例,當(dāng)多用戶同時請求一個服務(wù)時,容器會給每一個請求分配一個線程,這是多個線程會并發(fā)執(zhí)行該請求多對應(yīng)的成員方法,如果這個單例是無狀態(tài)的,那么就是線程安全的,如果這個單例是有狀態(tài)的就不是線程安全的。
解決有狀態(tài)單例線程安全的措施: 對于有狀態(tài)的單例可以實(shí)現(xiàn)全局共享,狀態(tài)的修改最好加鎖,保證線程的安全性。
5.Spring Bean 的自動裝配
1)首先用@Component注解類
@Component Class public Student{
2)在啟動類上添加@ComponentScan注解的類, spring才能自動裝配bean
@ComponentScan Class public Application{
3)開啟默認(rèn)掃描,spring將掃描由@Component注解的類,并且創(chuàng)建個實(shí)力
6.各種自動裝配模式的區(qū)別
1)no: 這是 Spring 框架的默認(rèn)設(shè)置,在缺省情況下,自動配置是通過“ref”屬性手動設(shè)定。
2)byName: 可以根據(jù) bean 名稱設(shè)置依賴關(guān)系。當(dāng)向一個 bean 中自動裝配一個屬性時,容器將根據(jù) bean 的名稱自動在在配置文件中查詢一個匹配的 bean。
3)byType: 可以根據(jù) bean 類型設(shè)置依賴關(guān)系。當(dāng)向一個 bean 中自動裝配一個屬性時,容器將根據(jù) bean 的類型自動在在配置文件中查詢一個匹配的 bean。
4)constructor: 在構(gòu)造函數(shù)參數(shù)的byType方式。**5)、autodetect:**如果找到默認(rèn)的構(gòu)造函數(shù),使用“自動裝配用構(gòu)造”; 否則,使用“按類型自動裝配”(在Spring3.0以后的版本此模式已被廢棄,已經(jīng)不再合法了)。
7.在Spring中可以注入null或空字符串嗎
沒問題
8.Spring框架中有哪些不同類型的事件(都繼承自ApplicationContextEvent)
1)上下文更新事件(ContextRefreshedEvent): 當(dāng)ApplicationContext初始化或刷新完成后觸發(fā)的事件。
2)上下文開始事件(ContextStartedEvent): 當(dāng)ApplicationContext啟動后觸發(fā)的事件
3)上下文停止事件(ContextStoppedEvent): 當(dāng)ApplicationContext停止后觸發(fā)的事件。
4)上下文關(guān)閉事件(ContextClosedEvent): 當(dāng)ApplicationContext被關(guān)閉時觸發(fā)該事件。當(dāng)容器被關(guān)閉時,其管理的所有單例Bean都被銷毀。
5)請求處理事件(RequestHandledEvent): 在Web應(yīng)用中,當(dāng)一個http請求(request)結(jié)束觸發(fā)該事件。
9.Spring框架中都用到了哪些設(shè)計(jì)模型
1)代理模式: AOP就是基于動態(tài)代理的,把公共的代碼抽象出來,封裝到一個模塊中用于代理,便于減少系統(tǒng)的重復(fù)代碼,降低模塊間的耦合度,并有利于未來的可拓展性和可維護(hù)性。
2)單例模式: 在spring配置文件中定義的bean默認(rèn)為單例模式。
使用單例模式的好處:
(1)對于頻繁使用的對象,可以省略創(chuàng)建對象所花費(fèi)的時間,減少系統(tǒng)的開銷;
(2)減少new操作的次數(shù),系統(tǒng)內(nèi)存使用率就會降低,這將減輕GC壓力,縮短GC停頓時間。
3)模板方法: 它定義了一系列操作的模型,子類繼承之后可以在模型 不變的情況下去實(shí)現(xiàn)自定義的操作。它可以用來解決代碼重復(fù)的問題。 Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 結(jié)尾的對數(shù)據(jù)庫操作的類,它們就使用到了模板模式。
4)適配器模式: 將一個接口轉(zhuǎn)換成客戶希望的另一個接口,適配器模式使接口不兼容的那些類可以一起工作。Spring AOP 的實(shí)現(xiàn)是基于代理模式,但是 Spring AOP 的增強(qiáng)或通知(Advice)使用到了適配器模式,與之相關(guān)的接口是AdvisorAdapter。Advice 常用的類型有:BeforeAdvice(目標(biāo)方法調(diào)用前,前置通知)、AfterAdvice(目標(biāo)方法調(diào)用后,后置通知)、AfterReturningAdvice(目標(biāo)方法執(zhí)行結(jié)束后,return之前)。
5)裝飾器模式: 裝飾者模式可以動態(tài)地給對象添加一些額外的屬性或行為。當(dāng)我們需要修改原有的功能,但我們又不愿直接去修改原有的代碼時,設(shè)計(jì)一個Decorator套在原有代碼外面。對于InputStream,ileInputStream 、BufferedInputStream都是對InputStream功能的擴(kuò)展。
6)觀察者模式: 它表示的是一種對象與對象之間具有依賴關(guān)系,當(dāng)一個對象發(fā)生改變的時候,這個對象所依賴的對象也會做出反應(yīng)。Spring 事件驅(qū)動模型就是觀察者模式很經(jīng)典的一個應(yīng)用。
7)工廠模式: BeanFactory用來創(chuàng)建對象的實(shí)例。
8)委派模式: Spring 提供了 DispatcherServlet 來對請求進(jìn)行分發(fā)。
10.FileSystemResource、 ClassPathResource 有何區(qū)別
ClassPathResource 在環(huán)境變量中讀取配置文件,F(xiàn)ileSystemResource 在配置文件中讀取配置文件。
11.使用Spring框架的好處是什么
1)輕量: Spring 是輕量的。
2)控制反轉(zhuǎn): 在Spring中對象有 IOC容器創(chuàng)建,并且通過配置注入到配置變量中。Spring通過控制反轉(zhuǎn)實(shí)現(xiàn)了低耦合。
3)面向切面的編程(AOP): Spring支持面向切面的編程,并且把應(yīng)用業(yè)務(wù)邏輯和系統(tǒng)服務(wù)分開。
4)容器(IOC): IOC可以創(chuàng)建、管理應(yīng)用中對象、對象生命周期和對象之間的關(guān)系。**5)MVC框架:**提供MVC框架,將控制邏輯代碼、數(shù)據(jù)存儲、試圖展示分層。
6)事務(wù)管理: Spring 提供一個持續(xù)的事務(wù)管理接口,封裝事物操作代碼。
7)異常處理: Spring可以全局捕捉異常。
12.Spring5 新特性
1) 支持JDK 8+和Java EE7+以上版本
2) 運(yùn)行時兼容JDK9
3) 運(yùn)行時兼容Java EE8 API
4) 反應(yīng)式編程模型
5) 使用注解進(jìn)行編程
6) 函數(shù)式編程
7) 提供專門的 HTTP/2 特性支持
8) 使用 Spring WebFlux 執(zhí)行集成測試
9) 使用 JUnit 5 執(zhí)行條件和并發(fā)測試
10) 支持Kotlin
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java實(shí)現(xiàn)查找文件和替換文件內(nèi)容
這篇文章主要為大家詳細(xì)介紹了Java語言如何實(shí)現(xiàn)查找文件和替換文件內(nèi)容功能,文中的示例代碼講解詳細(xì),感興趣的可以跟隨小編一起學(xué)習(xí)一下2022-08-08SpringBoot之配置logging日志及在控制臺中輸出過程
這篇文章主要介紹了SpringBoot之配置logging日志及在控制臺中輸出過程,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06Java實(shí)現(xiàn)線程的暫停和恢復(fù)的示例詳解
這幾天的項(xiàng)目中,客戶給了個需求,希望我可以開啟一個任務(wù),想什么時候暫停就什么時候暫停,想什么時候開始就什么時候開始,所以本文小編給大家介紹了Java實(shí)現(xiàn)線程的暫停和恢復(fù)的示例,需要的朋友可以參考下2023-11-11關(guān)于java中@Async異步調(diào)用詳細(xì)解析附代碼
本文主要介紹了java關(guān)于@Async異步調(diào)用詳細(xì)解析附代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07SpringBoot動態(tài)數(shù)據(jù)源連接測試的操作詳解
這篇文章主要介紹了SpringBoot動態(tài)數(shù)據(jù)源連接測試的操作步驟,文中通過代碼示例和圖文結(jié)合的方式給大家講解的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-03-03Spring Boot從Controller層進(jìn)行單元測試的實(shí)現(xiàn)
這篇文章主要介紹了Spring Boot從Controller層進(jìn)行單元測試的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04