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