溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

springboot1.X和2.X中怎么解決Bean名字相同時覆蓋

發(fā)布時間:2022-03-24 16:26:04 來源:億速云 閱讀:717 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“springboot1.X和2.X中怎么解決Bean名字相同時覆蓋”,在日常操作中,相信很多人在springboot1.X和2.X中怎么解決Bean名字相同時覆蓋問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”springboot1.X和2.X中怎么解決Bean名字相同時覆蓋”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

如何解決Bean名字相同時覆蓋

在2版本之前的版本,項(xiàng)目中有兩個相同名字的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實(shí)現(xiàn). 默認(rè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".【這里明確說明了默認(rèn)是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ù)又進(jìn)行了二次封裝,springboot中的allowBeanDefinitionOverriding是沒有初始化默認(rèn)值的,我們知道,java中的boolean類型不初始化時是false。

springboot中源代碼:

在SpringApplication類中

public class SpringApplication {
    ...
//boolean沒初始化,所以默認(rèn)為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進(jìn)行賦值為false,所以在springboot1.5.8版本中默認(rèn)就是支持名稱相同的bean的覆蓋。

覆蓋重寫 原有Spring Bean幾種方法

什么情況下要覆寫原有的Spring Bean ? 例如引入的第三方j(luò)ar包中的某個類有些問題,然有沒有源碼提供或者嫌編譯源碼太費(fèi)事,這個時間可以考慮覆寫原有的類。

方法1 直接在自己工程中建同包同類名的類進(jìn)行替換

方式簡單粗暴,可以直接覆蓋掉jar包中的類,spring項(xiàng)目會優(yōu)先加載自定義的類。

下面是覆蓋 flowable框架中的一個類 FlowableCookieFilter,主要是想修改它里面的redirectToLogin方法的業(yè)務(wù)邏輯。包路徑為 org.flowable.ui.common.filter, 直接工程里面新建一樣路徑一樣類名FlowableCookieFilter即可。

springboot1.X和2.X中怎么解決Bean名字相同時覆蓋

方法2 采用@Primary注解

該方法適用于接口實(shí)現(xiàn)類,自己創(chuàng)建一個原jar包接口的實(shí)現(xiàn)類,然后類上加上@Primary注解,spring則默認(rèn)加載該類實(shí)例化出的Bean。

下面的例子: 一個接口 RemoteIdmService,原先jar包中只有一個實(shí)現(xiàn)類 RemoteIdmServiceImpl,現(xiàn)在自己工程里面創(chuàng)建一個 CustomRemoteIdmServiceImpl 繼承RemoteIdmService接口,然后發(fā)現(xiàn)所有調(diào)用RemoteIdmService接口里面的方法實(shí)際調(diào)用走的是CustomRemoteIdmServiceImpl 里面的方法。

springboot1.X和2.X中怎么解決Bean名字相同時覆蓋

springboot1.X和2.X中怎么解決Bean名字相同時覆蓋

方法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包中的配置類:

springboot1.X和2.X中怎么解決Bean名字相同時覆蓋

直接繼承要覆蓋的類,自己重寫里面方法,使用@Component注入到spring中去

springboot1.X和2.X中怎么解決Bean名字相同時覆蓋

方法5 使用BeanDefinitionRegistryPostProcessor

關(guān)于 BeanDefinitionRegistryPostProcessor 、 BeanFactoryPostProcessor可以參考這篇文章:

BeanDefinitionRegistryPostProcessor 說白了就是可以在初始化Bean之前修改Bean的屬性,甚至替換原先準(zhǔn)備要實(shí)例化的bean。

實(shí)戰(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("------------------------");
    }
}

然后 實(shí)現(xiàn) BeanDefinitionRegistryPostProcessor 接口,修改原來bean定義,主要查看postProcessBeanDefinitionRegistry方法的實(shí)現(xiàn),先清空原bean定義,注冊我們自己的bean定義來達(dá)到替換的目的。

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的實(shí)例為value
        Map<String, Object> beanMap = configurableListableBeanFactory.getBeansWithAnnotation(RestController.class);
        logger.info("所有 RestController 的bean {}", beanMap);
    }
}

寫一個 業(yè)務(wù)類BusinessTestService測試一下,期望結(jié)果:所有用到 MyTestService的地方實(shí)際調(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();
    }
}

控制臺打印如下:

springboot1.X和2.X中怎么解決Bean名字相同時覆蓋

可以發(fā)現(xiàn),和我們期望的結(jié)果的一樣:所有用到 MyTestService的地方實(shí)際調(diào)用的變成了MyTestServiceIpml里面的方法 !

到此,關(guān)于“springboot1.X和2.X中怎么解決Bean名字相同時覆蓋”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI