您好,登錄后才能下訂單哦!
這篇文章主要介紹“springboot1.X和2.X中怎么解決Bean名字相同時覆蓋”,在日常操作中,相信很多人在springboot1.X和2.X中怎么解決Bean名字相同時覆蓋問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”springboot1.X和2.X中怎么解決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實現. 默認是允許*/ 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已經存在了 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時,只是記錄了一些日志,然后后來發現的這個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對這個參數又進行了二次封裝,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 ? 例如引入的第三方jar包中的某個類有些問題,然有沒有源碼提供或者嫌編譯源碼太費事,這個時間可以考慮覆寫原有的類。
方式簡單粗暴,可以直接覆蓋掉jar包中的類,spring項目會優先加載自定義的類。
下面是覆蓋 flowable框架中的一個類 FlowableCookieFilter,主要是想修改它里面的redirectToLogin方法的業務邏輯。包路徑為 org.flowable.ui.common.filter, 直接工程里面新建一樣路徑一樣類名FlowableCookieFilter即可。
方法2 采用@Primary注解
該方法適用于接口實現類,自己創建一個原jar包接口的實現類,然后類上加上@Primary注解,spring則默認加載該類實例化出的Bean。
下面的例子: 一個接口 RemoteIdmService,原先jar包中只有一個實現類 RemoteIdmServiceImpl,現在自己工程里面創建一個 CustomRemoteIdmServiceImpl 繼承RemoteIdmService接口,然后發現所有調用RemoteIdmService接口里面的方法實際調用走的是CustomRemoteIdmServiceImpl 里面的方法。
使用 @ComponentScan 里面的 excludeFilters 功能排除調用要替換的類,然后自己寫個類繼承替換的類即可。
下面的例子是替換掉 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) { // 覆寫該方法的業務邏輯 tokenCache.put(token.getId(), token); idmIdentityService.saveToken(token); return token; } @Override public void delete(Token token) { // 覆寫該方法的業務邏輯 tokenCache.invalidate(token.getId()); idmIdentityService.deleteToken(token.getId()); } @Override public Token getPersistentToken(String tokenId) { // 覆寫該方法的業務邏輯 return getPersistentToken(tokenId, false); } }
該場景針對,框架jar包中有@ConditionalOnMissingBean注解,這種注解是說明如果你也創建了一個一樣的Bean則框架就不自己再次創建這個bean了,這樣你可以覆寫自己的bean。原jar包中的配置類:
直接繼承要覆蓋的類,自己重寫里面方法,使用@Component注入到spring中去
關于 BeanDefinitionRegistryPostProcessor 、 BeanFactoryPostProcessor可以參考這篇文章:
BeanDefinitionRegistryPostProcessor 說白了就是可以在初始化Bean之前修改Bean的屬性,甚至替換原先準備要實例化的bean。
實戰演示:
假設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("------------------------"); } }
然后 實現 BeanDefinitionRegistryPostProcessor 接口,修改原來bean定義,主要查看postProcessBeanDefinitionRegistry方法的實現,先清空原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); // 如果有構造函數參數, 有幾個構造函數的參數就設置幾個 沒有就不用設置 beanDefinitionBuilder.addConstructorArgValue("構造參數1"); beanDefinitionBuilder.addConstructorArgValue("構造參數2"); beanDefinitionBuilder.addConstructorArgValue("構造參數3"); // 設置 init方法 沒有就不用設置 beanDefinitionBuilder.setInitMethodName("init"); // 設置 destory方法 沒有就不用設置 beanDefinitionBuilder.setDestroyMethodName("destory"); // 將Bean 的定義注冊到Spring環境 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); } }
寫一個 業務類BusinessTestService測試一下,期望結果:所有用到 MyTestService的地方實際調用的變成了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(); } }
控制臺打印如下:
可以發現,和我們期望的結果的一樣:所有用到 MyTestService的地方實際調用的變成了MyTestServiceIpml里面的方法 !
到此,關于“springboot1.X和2.X中怎么解決Bean名字相同時覆蓋”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。