springboot1.X和2.X中如何解決Bean名字相同時覆蓋
如何解決Bean名字相同時覆蓋
在2版本之前的版本,項目中有兩個相同名字的bean是可以啟動成功的,但是會有覆蓋問題
但是在2.X版本的時候會報錯:
could not be registered. A bean with that name has already been defined in class path resource
這時候解決辦法可以在配置文件中添加:
spring.main.allow-bean-definition-overriding=true
/** 是否允許使用相同名稱重新注冊不同的bean實現(xiàn). 默認是允許*/ private boolean allowBeanDefinitionOverriding = true; ? /** ?* Set whether it should be allowed to override bean definitions by registering ?* a different definition with the same name, automatically replacing the former. ?* If not, an exception will be thrown. This also applies to overriding aliases. ?* <p>Default is "true".【這里明確說明了默認是true】 ?* @see #registerBeanDefinition ?*/ public boolean isAllowBeanDefinitionOverriding() { ?? ?return this.allowBeanDefinitionOverriding; } ? @Override public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) ?? ??? ?throws BeanDefinitionStoreException { ? ?? ?Assert.hasText(beanName, "Bean name must not be empty"); ?? ?Assert.notNull(beanDefinition, "BeanDefinition must not be null"); ? ?? ?if (beanDefinition instanceof AbstractBeanDefinition) { ?? ??? ?try { ?? ??? ??? ?((AbstractBeanDefinition) beanDefinition).validate(); ?? ??? ?} ?? ??? ?catch (BeanDefinitionValidationException ex) { ?? ??? ??? ?throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, ?? ??? ??? ??? ??? ?"Validation of bean definition failed", ex); ?? ??? ?} ?? ?} ? ?? ?//bean加載到spring的工程中后,會存儲在beanDefinitionMap中,key是bean的名稱。 ?? ?BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName); ?? ?if (existingDefinition != null) {//不為空,說明相同名稱的bean已經(jīng)存在了 ?? ??? ?if (!isAllowBeanDefinitionOverriding()) {//如果不允許相同名稱的bean存在,則直接拋出異常 ?? ??? ??? ?throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition); ?? ??? ?} ?? ??? ?else if (existingDefinition.getRole() < beanDefinition.getRole()) { ?? ??? ??? ?// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE ?? ??? ??? ?if (logger.isInfoEnabled()) { ?? ??? ??? ??? ?logger.info("Overriding user-defined bean definition for bean '" + beanName + ?? ??? ??? ??? ??? ??? ?"' with a framework-generated bean definition: replacing [" + ?? ??? ??? ??? ??? ??? ?existingDefinition + "] with [" + beanDefinition + "]"); ?? ??? ??? ?} ?? ??? ?} ?? ??? ?else if (!beanDefinition.equals(existingDefinition)) { ?? ??? ??? ?if (logger.isDebugEnabled()) { ?? ??? ??? ??? ?logger.debug("Overriding bean definition for bean '" + beanName + ?? ??? ??? ??? ??? ??? ?"' with a different definition: replacing [" + existingDefinition + ?? ??? ??? ??? ??? ??? ?"] with [" + beanDefinition + "]"); ?? ??? ??? ?} ?? ??? ?} ?? ??? ?else { ?? ??? ??? ?if (logger.isTraceEnabled()) { ?? ??? ??? ??? ?logger.trace("Overriding bean definition for bean '" + beanName + ?? ??? ??? ??? ??? ??? ?"' with an equivalent definition: replacing [" + existingDefinition + ?? ??? ??? ??? ??? ??? ?"] with [" + beanDefinition + "]"); ?? ??? ??? ?} ?? ??? ?} ?? ??? ?//可見,上面allowBeanDefinitionOverriding =true時,只是記錄了一些日志,然后后來發(fā)現(xiàn)的這個bean,會覆蓋之前老的bean。 ?? ??? ?this.beanDefinitionMap.put(beanName, beanDefinition); ?? ?} ?? ?else { ?? ??? ?if (hasBeanCreationStarted()) { ?? ??? ??? ?// Cannot modify startup-time collection elements anymore (for stable iteration) ?? ??? ??? ?synchronized (this.beanDefinitionMap) { ?? ??? ??? ??? ?this.beanDefinitionMap.put(beanName, beanDefinition); ?? ??? ??? ??? ?List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1); ?? ??? ??? ??? ?updatedDefinitions.addAll(this.beanDefinitionNames); ?? ??? ??? ??? ?updatedDefinitions.add(beanName); ?? ??? ??? ??? ?this.beanDefinitionNames = updatedDefinitions; ?? ??? ??? ??? ?if (this.manualSingletonNames.contains(beanName)) { ?? ??? ??? ??? ??? ?Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames); ?? ??? ??? ??? ??? ?updatedSingletons.remove(beanName); ?? ??? ??? ??? ??? ?this.manualSingletonNames = updatedSingletons; ?? ??? ??? ??? ?} ?? ??? ??? ?} ?? ??? ?} ?? ??? ?else { ?? ??? ??? ?// Still in startup registration phase ?? ??? ??? ?this.beanDefinitionMap.put(beanName, beanDefinition); ?? ??? ??? ?this.beanDefinitionNames.add(beanName); ?? ??? ??? ?this.manualSingletonNames.remove(beanName); ?? ??? ?} ?? ??? ?this.frozenBeanDefinitionNames = null; ?? ?} ? ?? ?if (existingDefinition != null || containsSingleton(beanName)) { ?? ??? ?resetBeanDefinition(beanName); ?? ?} }
上面貼出來的是spring的代碼,而springboot2.X對這個參數(shù)又進行了二次封裝,springboot中的allowBeanDefinitionOverriding是沒有初始化默認值的,我們知道,java中的boolean類型不初始化時是false。
springboot中源代碼:
在SpringApplication類中
public class SpringApplication { ? ? ... //boolean沒初始化,所以默認為false private boolean allowBeanDefinitionOverriding; ...? ?private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { ? ? ? ? context.setEnvironment(environment); ? ? ? ? this.postProcessApplicationContext(context); ? ? ? ? this.applyInitializers(context); ? ? ? ? listeners.contextPrepared(context); ? ? ? ? if (this.logStartupInfo) { ? ? ? ? ? ? this.logStartupInfo(context.getParent() == null); ? ? ? ? ? ? this.logStartupProfileInfo(context); ? ? ? ? } ? ? ? ? ? ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); ? ? ? ? beanFactory.registerSingleton("springApplicationArguments", applicationArguments); ? ? ? ? if (printedBanner != null) { ? ? ? ? ? ? beanFactory.registerSingleton("springBootBanner", printedBanner); ? ? ? ? } ? ? ? ?//將false值傳過去 ? ? ? ? if (beanFactory instanceof DefaultListableBeanFactory) { ? ? ? ? ? ? ((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); ? ? ? ? } ? ? ? ? ? if (this.lazyInitialization) { ? ? ? ? ? ? context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); ? ? ? ? } ? ? ? ? ? Set<Object> sources = this.getAllSources(); ? ? ? ? Assert.notEmpty(sources, "Sources must not be empty"); ? ? ? ? this.load(context, sources.toArray(new Object[0])); ? ? ? ? listeners.contextLoaded(context); ? ? }
而在1.5.8版本中,SpringApplication中,沒有allowBeanDefinitionOverriding屬性,因此在prepareContext方法中也就沒有對allowBeanDefinitionOverriding進行賦值為false,所以在springboot1.5.8版本中默認就是支持名稱相同的bean的覆蓋。
覆蓋重寫 原有Spring Bean幾種方法
什么情況下要覆寫原有的Spring Bean ? 例如引入的第三方j(luò)ar包中的某個類有些問題,然有沒有源碼提供或者嫌編譯源碼太費事,這個時間可以考慮覆寫原有的類。
方法1 直接在自己工程中建同包同類名的類進行替換
方式簡單粗暴,可以直接覆蓋掉jar包中的類,spring項目會優(yōu)先加載自定義的類。
下面是覆蓋 flowable框架中的一個類 FlowableCookieFilter,主要是想修改它里面的redirectToLogin方法的業(yè)務(wù)邏輯。包路徑為 org.flowable.ui.common.filter, 直接工程里面新建一樣路徑一樣類名FlowableCookieFilter即可。
方法2 采用@Primary注解
該方法適用于接口實現(xiàn)類,自己創(chuàng)建一個原jar包接口的實現(xiàn)類,然后類上加上@Primary注解,spring則默認加載該類實例化出的Bean。
下面的例子: 一個接口 RemoteIdmService,原先jar包中只有一個實現(xiàn)類 RemoteIdmServiceImpl,現(xiàn)在自己工程里面創(chuàng)建一個 CustomRemoteIdmServiceImpl 繼承RemoteIdmService接口,然后發(fā)現(xiàn)所有調(diào)用RemoteIdmService接口里面的方法實際調(diào)用走的是CustomRemoteIdmServiceImpl 里面的方法。
方法3 排除需要替換的jar包中的類
使用 @ComponentScan 里面的 excludeFilters 功能排除調(diào)用要替換的類,然后自己寫個類繼承替換的類即可。
下面的例子是替換掉 jar包中的PersistentTokenServiceImpl類
@SpringBootApplication @ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {PersistentTokenServiceImpl.class})}) public class Application { public static void main(String[] args) { new SpringApplication(Test.class).run(args); } }
@Service public class MyPersistentTokenServiceImpl extends PersistentTokenServiceImpl{ @Override public Token saveAndFlush(Token token) { // 覆寫該方法的業(yè)務(wù)邏輯 tokenCache.put(token.getId(), token); idmIdentityService.saveToken(token); return token; } @Override public void delete(Token token) { // 覆寫該方法的業(yè)務(wù)邏輯 tokenCache.invalidate(token.getId()); idmIdentityService.deleteToken(token.getId()); } @Override public Token getPersistentToken(String tokenId) { // 覆寫該方法的業(yè)務(wù)邏輯 return getPersistentToken(tokenId, false); } }
方法4 @Bean 覆蓋
該場景針對,框架jar包中有@ConditionalOnMissingBean注解,這種注解是說明如果你也創(chuàng)建了一個一樣的Bean則框架就不自己再次創(chuàng)建這個bean了,這樣你可以覆寫自己的bean。原jar包中的配置類:
直接繼承要覆蓋的類,自己重寫里面方法,使用@Component注入到spring中去
方法5 使用BeanDefinitionRegistryPostProcessor
關(guān)于 BeanDefinitionRegistryPostProcessor 、 BeanFactoryPostProcessor可以參考這篇文章:
BeanDefinitionRegistryPostProcessor 說白了就是可以在初始化Bean之前修改Bean的屬性,甚至替換原先準(zhǔn)備要實例化的bean。
實戰(zhàn)演示:
假設(shè)jar包中有一個類 MyTestService,正常情況下它會被spring自動掃描到注入IOC容器中去。
package com.middol.mytest.config.beantest.register.jar; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; /** ?* @author guzt ?*/ @Service("myTestService") public class MyTestService { ? ? private String name1; ? ? private String name2; ? ? private String name3; ? ? public MyTestService() { ? ? ? ? this.name1 = ""; ? ? ? ? this.name2 = ""; ? ? ? ? this.name3 = ""; ? ? } ? ? public MyTestService(String name1, String name2, String name3) { ? ? ? ? this.name1 = name1; ? ? ? ? this.name2 = name2; ? ? ? ? this.name3 = name3; ? ? } ? ? @PostConstruct ? ? public void init() { ? ? ? ? System.out.println("MyTestService init"); ? ? } ? ? @PreDestroy ? ? public void destory() { ? ? ? ? System.out.println("MyTestService destroy"); ? ? } ? ? public void show() { ? ? ? ? System.out.println("------------------------"); ? ? ? ? System.out.println("我是jar中通過注解@Service主動加入Spring的IOC里面的"); ? ? ? ? System.out.println("------------------------"); ? ? } ? ? public String getName1() { ? ? ? ? return name1; ? ? } ? ? public void setName1(String name1) { ? ? ? ? this.name1 = name1; ? ? } ? ? public String getName2() { ? ? ? ? return name2; ? ? } ? ? public void setName2(String name2) { ? ? ? ? this.name2 = name2; ? ? } ? ? public String getName3() { ? ? ? ? return name3; ? ? } ? ? public void setName3(String name3) { ? ? ? ? this.name3 = name3; ? ? } }
自己工程中繼承該類,并且覆寫里面的show中的方法
package com.middol.mytest.config.beantest.register; import com.middol.mytest.config.beantest.register.jar.MyTestService; /** ?* @author guzt ?*/ public class MyTestServiceIpml extends MyTestService { ? ? public MyTestServiceIpml() { ? ? } ? ? public MyTestServiceIpml(String name1, String name2, String name3) { ? ? ? ? super(name1, name2, name3); ? ? } ? ? @Override ? ? public void show() { ? ? ? ? System.out.println("------------------------"); ? ? ? ? System.out.println("我是被BeanDefinitionRegistry手動注冊到Spring的IOC里面的"); ? ? ? ? System.out.println("------------------------"); ? ? } }
然后 實現(xiàn) BeanDefinitionRegistryPostProcessor 接口,修改原來bean定義,主要查看postProcessBeanDefinitionRegistry方法的實現(xiàn),先清空原bean定義,注冊我們自己的bean定義來達到替換的目的。
package com.middol.mytest.config.beantest.register; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RestController; import java.util.Map; /** ?* @author amdin ?*/ @Component public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { ? ? private Logger logger = LoggerFactory.getLogger(this.getClass()); ? ? @Override ? ? public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { ? ? ? ? logger.info("bean 定義查看和修改..."); ? ? ? ? String beanName = "myTestService"; ? ? ? ? // 先移除原來的bean定義 ? ? ? ? beanDefinitionRegistry.removeBeanDefinition(beanName); ? ? ? ? // 注冊我們自己的bean定義 ? ? ? ? BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(MyTestServiceIpml.class); ? ? ? ? // 如果有構(gòu)造函數(shù)參數(shù), 有幾個構(gòu)造函數(shù)的參數(shù)就設(shè)置幾個 ?沒有就不用設(shè)置 ? ? ? ? beanDefinitionBuilder.addConstructorArgValue("構(gòu)造參數(shù)1"); ? ? ? ? beanDefinitionBuilder.addConstructorArgValue("構(gòu)造參數(shù)2"); ? ? ? ? beanDefinitionBuilder.addConstructorArgValue("構(gòu)造參數(shù)3"); ? ? ? ? // 設(shè)置 init方法 沒有就不用設(shè)置 ? ? ? ? beanDefinitionBuilder.setInitMethodName("init"); ? ? ? ? // 設(shè)置 destory方法 沒有就不用設(shè)置 ? ? ? ? beanDefinitionBuilder.setDestroyMethodName("destory"); ? ? ? ? // 將Bean 的定義注冊到Spring環(huán)境 ? ? ? ? beanDefinitionRegistry.registerBeanDefinition("myTestService", beanDefinitionBuilder.getBeanDefinition()); ? ? } ? ? @Override ? ? public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { ? ? ? ? // bean的名字為key, bean的實例為value ? ? ? ? Map<String, Object> beanMap = configurableListableBeanFactory.getBeansWithAnnotation(RestController.class); ? ? ? ? logger.info("所有 RestController 的bean {}", beanMap); ? ? } }
寫一個 業(yè)務(wù)類BusinessTestService測試一下,期望結(jié)果:所有用到 MyTestService的地方實際調(diào)用的變成了MyTestServiceIpml里面的方法。
package com.middol.mytest.config.beantest.register; import com.middol.mytest.config.beantest.register.jar.MyTestService; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.Resource; /** ?* @author guzt ?*/ @Service public class BusinessTestService { ? ? @Resource ? ? private MyTestService myTestService; ? ? @PostConstruct ? ? public void init() { ? ? ? ? System.out.println(myTestService.getName1()); ? ? ? ? System.out.println(myTestService.getName2()); ? ? ? ? System.out.println(myTestService.getName3()); ? ? ? ? // 看看到底是哪一個Bean ? ? ? ? myTestService.show(); ? ? } }
控制臺打印如下:
可以發(fā)現(xiàn),和我們期望的結(jié)果的一樣:所有用到 MyTestService的地方實際調(diào)用的變成了MyTestServiceIpml里面的方法 !
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java多線程中不同條件下編寫生產(chǎn)消費者模型方法介紹
這篇文章主要介紹了Java多線程中不同條件下編寫生產(chǎn)消費者模型方法介紹,介紹了生產(chǎn)消費者模型,然后分享了相關(guān)代碼示例,具有一定參考價值,需要的朋友可以了解下。2017-11-11使用Idea或Datagrip導(dǎo)入excel數(shù)據(jù)的方法
這篇文章主要介紹了使用Idea或Datagrip導(dǎo)入excel數(shù)據(jù)的方法,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11Spring MVC+FastJson+hibernate-validator整合的完整實例教程
這篇文章主要給大家介紹了關(guān)于Spring MVC+FastJson+hibernate-validator整合的完整實例教程,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-04-04idea運行tomcat報錯找不到catalina.bat,系統(tǒng)找不到指定的文件問題
這篇文章主要介紹了idea運行tomcat報錯找不到catalina.bat,系統(tǒng)找不到指定的文件問題,具有很好的參考價值,希望對大家有所幫助,2023-11-11Spring的@Autowired加到接口上但獲取的是實現(xiàn)類的問題
這篇文章主要介紹了Spring的@Autowired加到接口上但獲取的是實現(xiàn)類的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10