Spring?Prototype?Bean的四種正確使用方式
大家有沒有遇到過“明明配置了Prototype作用域,調(diào)用時(shí)卻始終拿到同一個(gè)實(shí)例”的情況? 今天我們就從這個(gè)常見錯(cuò)誤場景入題,一步步學(xué)會(huì)Prototype Bean的正確使用方式,搞懂每種方式的優(yōu)劣,再也不踩坑!
一、高頻踩坑:單例Bean依賴Prototype Bean
我們先看一個(gè)實(shí)際開發(fā)中最容易寫錯(cuò)的代碼,大家可以對(duì)照自己的項(xiàng)目,看看有沒有類似寫法:
// 1. 定義Prototype Bean(
@Component
@Scope("prototype")
public class PrototypeBean {
private String id;
// 構(gòu)造方法,每次實(shí)例化時(shí)生成唯一ID
public PrototypeBean() {
this.id = UUID.randomUUID().toString().substring(0, 8);
}
// getter方法
public String getId() {
return id;
}
}
// 2. 定義Singleton Bean依賴上面的Prototype Bean
@Service
public class SingletonService {
// 直接用@Autowired注入Prototype Bean
@Autowired
private PrototypeBean prototypeBean;
// 獲取Prototype Bean的ID
public String getPrototypeBeanId() {
return prototypeBean.getId();
}
}
// 3. 測試代碼
@SpringBootTest
public class PrototypeTest {
@Autowired
private SingletonService singletonService;
@Test
public void testErrorUsage() {
// 多次調(diào)用,返回不同的ID(新實(shí)例)
String id1 = singletonService.getPrototypeBeanId();
String id2 = singletonService.getPrototypeBeanId();
String id3 = singletonService.getPrototypeBeanId();
// 結(jié)果:三次ID完全相同,說明只創(chuàng)建了一個(gè)Prototype Bean實(shí)例
System.out.println("第一次ID:" + id1);
System.out.println("第二次ID:" + id2);
System.out.println("第三次ID:" + id3);
}
}
運(yùn)行測試后會(huì)發(fā)現(xiàn),三次獲取的ID完全一致——原因很簡單:Singleton Bean是全局單例,初始化時(shí)只會(huì)注入一次Prototype Bean,后續(xù)無論調(diào)用多少次,用的都是第一次注入的那個(gè)實(shí)例,Prototype Bean的“每次新實(shí)例”特性完全失效。
既然直接注入會(huì)踩坑,那我們該如何正確獲取Prototype Bean呢?接下來,我們就逐一講解四種正確使用方式。
二、Prototype Bean 四種正確使用方式(附代碼+優(yōu)劣)
方式一:通過ApplicationContextAware 編程式獲取
核心思路:讓服務(wù)類實(shí)現(xiàn)ApplicationContextAware接口,獲取Spring容器上下文,然后通過applicationContext.getBean()方法,每次主動(dòng)獲取Prototype Bean——因?yàn)槊看握{(diào)用getBean(),Spring都會(huì)創(chuàng)建一個(gè)新的Prototype實(shí)例。
//ApplicationContextAware類
@Service
public class PrototypeBeanServiceByApplicationAware implements ApplicationContextAware {
// Spring容器上下文
private ApplicationContext applicationContext;
// 實(shí)現(xiàn)接口方法,Spring會(huì)自動(dòng)注入ApplicationContext
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public PrototypeBean getPrototypeBean() {
// 每次調(diào)用getBean(),都會(huì)創(chuàng)建新的Prototype實(shí)例
return applicationContext.getBean(PrototypeBean.class);
}
}
// 3. 測試代碼
@Test
public void testApplicationContextAware() {
@Autowired
private PrototypeBeanServiceByApplicationAware service;
// 多次調(diào)用,觀察ID同
String id1 = service.getPrototypeBean().getId();
String id2 = service.getPrototypeBean().getId();
String id3 = service.getPrototypeBean().getId();
System.out.println("第一次ID:" + id1);
System.out.println("第二次ID:" + id2);
System.out.println("第三次ID:" + id3);
}
優(yōu)點(diǎn):編程方式,靈活度高,可根據(jù)業(yè)務(wù)邏輯動(dòng)態(tài)獲取Prototype Bean,不受注入方式限制。
缺點(diǎn):代碼耦合度高,服務(wù)類直接依賴Spring的ApplicationContext,脫離Spring容器后無法獨(dú)立測試;代碼冗長,每個(gè)需要的地方都要注入 ApplicationContext,或者創(chuàng)建PrototypeBeanServiceByApplicationAware 這種代理的方式
方式二:通過@Lookup 注解獲取
核心思路:使用Spring提供的**@Lookup**注解,Spring會(huì)自動(dòng)重寫該方法,每次調(diào)用方法時(shí),都會(huì)返回一個(gè)新的Prototype Bean實(shí)例。
// 用@Lookup注解的服務(wù)類
@Service
public class PrototypeBeanServiceByLookup {
@Lookup
public PrototypeBean getPrototypeBean() {
return null;
}
}
// 3. 測試代碼
@Test
public void testLookup() {
@Autowired
private PrototypeBeanServiceByLookup service;
String id1 = service.getPrototypeBean().getId();
String id2 = service.getPrototypeBean().getId();
String id3 = service.getPrototypeBean().getId();
System.out.println("第一次ID:" + id1);
System.out.println("第二次ID:" + id2);
System.out.println("第三次ID:" + id3);
}
補(bǔ)充說明:@Lookup注解也可以用在抽象方法上,spring會(huì)自動(dòng)實(shí)現(xiàn)該方法。
優(yōu)點(diǎn):相比方式一代碼簡潔,只需要一個(gè)注解和一個(gè)方法;
缺點(diǎn):要寫個(gè)空方法,讓人感覺奇怪
方式三:通過ObjectFactory 獲取
核心思路:使用Spring提供的ObjectFactory接口,它是一個(gè)“工廠類”,專門用于獲取Bean實(shí)例,注入ObjectFactory后,每次調(diào)用getObject()方法,都會(huì)返回一個(gè)新的Prototype Bean。
// 用ObjectFactory的服務(wù)類
@Service
public class PrototypeBeanServiceByObjectFactory {
// 注入ObjectFactory
@Autowired
private ObjectFactory<PrototypeBean> prototypeBeanObjectFactory;
// 每次調(diào)用getObject(),獲取新的Prototype實(shí)例
public PrototypeBean getPrototypeBean() {
return prototypeBeanObjectFactory.getObject();
}
}
// 3. 測試代碼
@Test
public void testObjectFactory() {
@Autowired
private PrototypeBeanServiceByObjectFactory service;
String id1 = service.getPrototypeBean().getId();
String id2 = service.getPrototypeBean().getId();
String id3 = service.getPrototypeBean().getId();
System.out.println("第一次ID:" + id1);
System.out.println("第二次ID:" + id2);
System.out.println("第三次ID:" + id3);
}
補(bǔ)充說明:ObjectFactory是Spring的輕量級(jí)工廠接口,僅提供getObject()一個(gè)方法,專門用于“延遲獲取Bean”,適合用于獲取Prototype Bean,既解耦,又不會(huì)增加過多開銷。
優(yōu)點(diǎn):相比于@Lookup,不需要再寫個(gè)方法,直接注入即可
方式四:通過ObjectProvider 獲?。ㄍ扑])
ObjectProvider是Spring 4.3+新增的接口,是ObjectFactory的增強(qiáng)版,不僅能獲取Bean實(shí)例,還提供了多種靈活的獲取方法(比如判斷Bean是否存在、獲取唯一Bean等),是目前獲取Prototype Bean的推薦方式。
// 用ObjectProvider的服務(wù)類
@Service
public class PrototypeBeanServiceByObjectProvider {
// 注入ObjectProvider
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanObjectProvider;
// 獲取Prototype實(shí)例(和ObjectFactory類似)
public PrototypeBean getPrototypeBean() {
return prototypeBeanObjectProvider.getIfAvailable();
}
}
// 3. 測試代碼
@Test
public void testObjectProvider() {
@Autowired
private PrototypeBeanServiceByObjectProvider service;
String id1 = service.getPrototypeBean().getId();
String id2 = service.getPrototypeBean().getId();
String id3 = service.getPrototypeBean().getId();
System.out.println("第一次ID:" + id1);
System.out.println("第二次ID:" + id2);
System.out.println("第三次ID:" + id3);
}
補(bǔ)充說明:getIfUnique()是獲取bean的另一種方式,簡單說下和getIfAvailable區(qū)別:
- getIfAvailable():“有就拿,沒有就算了”(返回null),如果同類型bean有多個(gè),則拋異常;
- getIfUnique():“有且只有一個(gè)”,否則返回null,如果同類型bean有多個(gè)也返回null。
優(yōu)點(diǎn):功能強(qiáng)大,支持多種獲取方式,是Spring推薦的方式。
四種方式我們都講完了,每種方式都有自己的優(yōu)劣和適用場景。但實(shí)際開發(fā)中,我們常會(huì)遇到同一類型存在多個(gè)Prototype Bean的情況(比如通過@Bean定義多個(gè)同類型實(shí)例),這種場景下該如何精準(zhǔn)獲取目標(biāo)Bean呢?接下來我們說下這種特殊場景的處理方法。
三、特殊場景:同一類型存在多個(gè)Prototype Bean的處理
場景代碼如下:
@Configuration
public class Config {
// 第一個(gè)Prototype Bean,bean名稱默認(rèn)是方法名prototypeBean
@Bean
@Scope("prototype")
public PrototypeBean prototypeBean() {
return new PrototypeBean();
}
// 第二個(gè)Prototype Bean,bean名稱為prototypeBeanA
@Bean
@Scope("prototype")
public PrototypeBean prototypeBeanA() {
return new PrototypeBean();
}
// 第三個(gè)Prototype Bean,bean名稱為prototypeBeanB
@Bean
@Scope("prototype")
public PrototypeBean prototypeBeanB() {
return new PrototypeBean();
}
}
此時(shí)容器中存在3個(gè)PrototypeBean類型的Bean。此時(shí)直接用之前的方式獲取會(huì)出現(xiàn)問題,可能返回null,可能報(bào)錯(cuò),我們針對(duì)四種方式,分別給出解決方案:
1. 方式一(ApplicationContextAware):通過getBean指定bean名稱
核心思路:在調(diào)用applicationContext.getBean()方法時(shí),增加一個(gè)參數(shù)(bean名稱),精準(zhǔn)指定要獲取的目標(biāo)Bean,避免歧義。
@Service
public class PrototypeBeanServiceByApplicationAware implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
// 分別獲取不同名稱的Prototype Bean
public PrototypeBean getPrototypeBean() {
// 指定bean名稱為prototypeBean
return applicationContext.getBean("prototypeBean", PrototypeBean.class);
}
}
2. 方式二(@Lookup注解):通過@Lookup指定bean名稱
核心思路:在@Lookup注解中添加value屬性,指定目標(biāo)Bean的名稱。
@Service
public class PrototypeBeanServiceByLookup {
// 指定獲取名稱為prototypeBean的Bean
@Lookup("prototypeBean")
public PrototypeBean getPrototypeBean() {
return null;
}
}
3. 方式三、方式四(ObjectFactory/ObjectProvider):通過字段名匹配bean名稱
核心思路:Spring自動(dòng)注入時(shí),會(huì)根據(jù)字段名匹配容器中的Bean名稱,即可精準(zhǔn)獲取對(duì)應(yīng)實(shí)例。
@Service
public class PrototypeBeanServiceByObjectFactory {
// 字段名=bean名稱,獲取該名稱的Bean
@Autowired
private ObjectFactory<PrototypeBean> prototypeBean;
// 獲取方法
public PrototypeBean getPrototypeBean() {
return prototypeBean.getObject();
}
}
總結(jié):同一類型存在多個(gè)Prototype Bean時(shí),核心是“通過名稱定位Bean”。
四、總結(jié):四種方式對(duì)比與選擇
| 使用方式 | 核心優(yōu)點(diǎn) | 核心缺點(diǎn) |
|---|---|---|
| ApplicationContextAware | 靈活度高,可動(dòng)態(tài)獲取 | 耦合Spring容器,測試不便 |
| @Lookup注解 | 代碼簡潔 | 需要添加一個(gè)“雞肋”方法 |
| ObjectFactory | 直接注入,簡便 | 多bean時(shí),如果不處理會(huì)拋異常 |
| ObjectProvider | 功能強(qiáng)大,靈活,Spring推薦 | 無 |
總結(jié):日常開發(fā)中,優(yōu)先選擇ObjectProvider;如果場景簡單,可選擇@Lookup或ObjectFactory;只有需要?jiǎng)討B(tài)Bean獲取邏輯時(shí),才考慮ApplicationContextAware。
以上就是Spring Prototype Bean的四種正確使用方式的詳細(xì)內(nèi)容,更多關(guān)于Spring Prototype Bean使用方式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java yaml轉(zhuǎn)properties工具類方式
這篇文章主要介紹了java yaml轉(zhuǎn)properties工具類方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
springboot結(jié)合ehcache防止惡意刷新請求的實(shí)現(xiàn)
這篇文章主要介紹了springboot結(jié)合ehcache防止惡意刷新請求的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
利用ScriptEngineManager實(shí)現(xiàn)字符串公式靈活計(jì)算的方法
今天小編就為大家分享一篇利用ScriptEngineManager實(shí)現(xiàn)字符串公式靈活計(jì)算的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-07-07
基于Java實(shí)現(xiàn)本地APK文件搜索與HTTP下載服務(wù)
在移動(dòng)應(yīng)用開發(fā)和測試過程中,經(jīng)常需要從本地服務(wù)器獲取最新的 APK 安裝包,本文將詳細(xì)介紹如何使用 Java 構(gòu)建一個(gè)能夠搜索本地最新 APK 文件并通過 HTTP 請求提供下載服務(wù)的應(yīng)用2025-07-07
SpringCloud搭建Eureka服務(wù)模塊的過程
Eureka在分布式系統(tǒng)中起到了連接各個(gè)微服務(wù)的紐帶作用,使得服務(wù)之間的交互變得更加靈活、可靠,本文將深入探討如何使用Spring?Cloud,逐步引導(dǎo)讀者完成Eureka服務(wù)模塊的搭建,感興趣的朋友跟隨小編一起看看吧2024-02-02
spring boot springjpa 支持多個(gè)數(shù)據(jù)源的實(shí)例代碼
這篇文章主要介紹了spring boot springjpa 支持多個(gè)數(shù)據(jù)源的實(shí)例代碼,需要的朋友可以參考下2018-04-04
Java AbstractMethodError案例分析詳解
這篇文章主要介紹了Java AbstractMethodError案例分析詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
Mybatis調(diào)用MySQL存儲(chǔ)過程的簡單實(shí)現(xiàn)
本篇文章主要介紹了Mybatis調(diào)用MySQL存儲(chǔ)過程的簡單實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-04-04

