springboot項(xiàng)目中出現(xiàn)同名bean異常報錯的解決方法
前言
最近業(yè)務(wù)部門接手供方的項(xiàng)目過來二開,其中有個認(rèn)證實(shí)現(xiàn)因?yàn)闃I(yè)務(wù)需要,需要替換原有供方實(shí)現(xiàn)的邏輯。大概偽代碼如下。供方提供的接口以及默認(rèn)實(shí)現(xiàn)形如下
public interface AuthCodeService { default Boolean check() { return true; } } @Service("authCodeService") public class AuthCodeImpl implements AuthCodeService { public Boolean check() { // doBiz return true; } }
業(yè)務(wù)的替換實(shí)現(xiàn)如下
@Service public class BizAuthCodeImpl implements AuthCodeService { public Boolean check() { // doOtherBiz return true; } }
然而項(xiàng)目運(yùn)行的時候,發(fā)現(xiàn)走的認(rèn)證邏輯始終是供方的邏輯,而非業(yè)務(wù)重寫后的邏輯。
平時因?yàn)楦鷺I(yè)務(wù)的技術(shù)負(fù)責(zé)人走得比較近,他就私下找我交流一下思路。一開始我給他提的建議是說在你定制的業(yè)務(wù)類上加@Primary試下,他說他加了但沒效果。
于是我就跟他說不然你直接改供方源碼的默認(rèn)實(shí)現(xiàn),他給的答復(fù)供方?jīng)]提供源碼,只提供jar。我就跟他說,這也可以改,你項(xiàng)目創(chuàng)建一個和供方實(shí)現(xiàn)一模一樣的類,就是包名和類名一模一樣,利用類的加載順序?qū)崿F(xiàn)。技術(shù)負(fù)責(zé)人又覺得這樣不好。
后面那個技術(shù)負(fù)責(zé)人想了一個方式,就是他將業(yè)務(wù)定制bean名稱和供方提供的bean名稱一樣,形如下
@Service("authCodeService") public class BizAuthCodeImpl implements AuthCodeService { public Boolean check() { // doOtherBiz return true; } }
然后項(xiàng)目啟動,直接報了如下錯
org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'authCodeService' for bean class
他就跟我說這個異常怎么修復(fù),鋪墊了這么久,引來了今天要聊的話題,同名bean異常報錯如何修復(fù)
解決思路
首先拋出一個觀點(diǎn),在同個spring容器中,是不能出現(xiàn)同名的bean,因此解決的思路要么搞成不同的spring容器,要么就是排除多個同名的bean,只保留自己想要的那個。要么就是將bean改個名字。今天介紹的思路就是排除同名bean,只保留自己想要的bean
實(shí)現(xiàn)方法
1、方法一:通過@ComponentScan進(jìn)行排除
示例配置
在springboot的啟動類上加上形如下內(nèi)容
@ComponentScan(basePackages = {"com.github.lybgeek"},excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = AuthCodeImpl.class)})
這邊有個注意點(diǎn)是當(dāng)你啟動類上同時存在@SpringBootApplication和@ComponentScan注解時,@ComponentScan注解指定的掃描包路徑會覆蓋@SpringBootApplication的包路徑。
我將第一種方案告訴業(yè)務(wù)技術(shù)負(fù)責(zé)人后,他試了一下,果然沒報錯,但是后面出現(xiàn)一個問題,他說@SpringBootApplication的屬性exclude()失效了,導(dǎo)致他項(xiàng)目要排除的自動裝配類失效了。于是就有了第二種方案
2、方法二:自定義TypeExcludeFilter
我們點(diǎn)開@SpringBootApplication,可以看到如下內(nèi)容
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {
既然@SpringBootApplication和@ComponentScan同時標(biāo)注在啟動類上會有一定沖突,我們就遵循@SpringBootApplication提供的擴(kuò)展方案就好了,自己寫一個TypeExcludeFilter進(jìn)行排除
實(shí)現(xiàn)步驟
1、自定義TypeExcludeFilter
public class CustomTypeExcludeFilter extends TypeExcludeFilter { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { String className = metadataReader.getClassMetadata().getClassName(); return AuthCodeImpl.class.getName().equals(className); } }
2、將自定義TypeExcludeFilter注入到spring容器 中
這邊有個特別需要注意的細(xì)節(jié)點(diǎn),因?yàn)門ypeExcludeFilter是要排除bean的,因此他注入的時機(jī)至少要在其他bean注入之前,具體來說就是在容器上下文refresh執(zhí)行之前,就得完成注入。在refresh之前執(zhí)行的擴(kuò)展點(diǎn)有很多,我們就挑一個,我們以實(shí)現(xiàn)ApplicationContextInitializer為例
public class CustomTypeExcludeFilterApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext applicationContext) { DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory(); BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(CustomTypeExcludeFilter.class); AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition(); defaultListableBeanFactory.registerBeanDefinition("customTypeExcludeFilter",beanDefinition); } }
3、將ApplicationContextInitializer 的實(shí)現(xiàn)類放在/META-INF/spring.factories
org.springframework.context.ApplicationContextInitializer=\ com.github.lybgeek.context.CustomTypeExcludeFilterApplicationContextInitializer
按照上面三步執(zhí)行,就可以排除自己想排除的bean
總結(jié)
當(dāng)項(xiàng)目中出現(xiàn)同名bean沖突時,如果可以的話,就盡量換個其他bean名稱來解決
后面業(yè)務(wù)負(fù)責(zé)人并沒有采用我上述的方案,我們回歸業(yè)務(wù)負(fù)責(zé)人他們項(xiàng)目訴求,他們的需求是要他們自定義認(rèn)證的邏輯能生效,而非解決同名bean沖突。
業(yè)務(wù)負(fù)責(zé)人他們最后的方案是通過加@Primary注解解決,他之前加了覺得沒生效,是因?yàn)樗麄冺?xiàng)目引的自定義認(rèn)證邏輯的舊包,那個舊包沒加@Primary注解,后面把包升級就解決了
以上就是springboot項(xiàng)目中出現(xiàn)同名bean異常報錯的解決方法的詳細(xì)內(nèi)容,更多關(guān)于springboot出現(xiàn)同名bean的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java 并發(fā)編程學(xué)習(xí)筆記之核心理論基礎(chǔ)
編寫優(yōu)質(zhì)的并發(fā)代碼是一件難度極高的事情。Java語言從第一版本開始內(nèi)置了對多線程的支持,這一點(diǎn)在當(dāng)年是非常了不起的,但是當(dāng)我們對并發(fā)編程有了更深刻的認(rèn)識和更多的實(shí)踐后,實(shí)現(xiàn)并發(fā)編程就有了更多的方案和更好的選擇。本文是對并發(fā)編程的核心理論做了下小結(jié)2016-05-05Java Swing JComboBox下拉列表框的示例代碼
這篇文章主要介紹了Java Swing JComboBox下拉列表框的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12Spring?Data?JPA實(shí)現(xiàn)持久化存儲數(shù)據(jù)到數(shù)據(jù)庫的示例代碼
Spring Data JPA是Spring基于JPA規(guī)范的基礎(chǔ)上封裝的?套 JPA 應(yīng)?框架,可使開發(fā)者?極簡的代碼即可實(shí)現(xiàn)對數(shù)據(jù)庫的訪問和操作。本文我們來了解如何用Spring?Data?JPA框架實(shí)現(xiàn)數(shù)據(jù)持久化存儲到數(shù)據(jù)庫,感興趣的可以了解一下2022-04-04spring-data-redis 動態(tài)切換數(shù)據(jù)源的方法
最近遇到了一個麻煩的需求,我們需要一個微服務(wù)應(yīng)用同時訪問兩個不同的 Redis 集群,一般情況下我們會怎么處理呢,下面通過場景分析給大家介紹spring-data-redis 動態(tài)切換數(shù)據(jù)源的方法,感興趣的朋友一起看看吧2021-08-08springboot項(xiàng)目部署到k8s上的方法步驟
本文主要介紹了springboot項(xiàng)目部署到k8s上的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05