溫馨提示×

溫馨提示×

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

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

SpringBoot基礎(chǔ)篇之重名Bean的解決與多實(shí)例選擇

發(fā)布時(shí)間:2020-07-26 15:40:21 來源:網(wǎng)絡(luò) 閱讀:1355 作者:java架構(gòu)師1 欄目:編程語言

當(dāng)通過接口的方式注入Bean時(shí),如果有多個(gè)子類的bean存在時(shí),具體哪個(gè)bean會被注入呢?系統(tǒng)中能否存在兩個(gè)重名的bean呢?如果可以,那么怎么選擇引入呢?如果不行的話又該怎么避免上面的問題呢?

<!-- more -->

I. 多實(shí)例Bean的選擇
這個(gè)場景可以說是比較常見的,現(xiàn)在提倡面向接口編程嘛,當(dāng)一個(gè)接口有多個(gè)實(shí)例時(shí),怎么注入和引用就需要我們額外關(guān)注下了

  1. 基本使用姿勢
    首先定義一個(gè)接口和兩個(gè)簡單的實(shí)現(xiàn)類,并演示一下我們通常的用法

一個(gè)輸出的接口定義如下

public interface IPrint {
    void print(String msg);
}

對應(yīng)給兩個(gè)實(shí)現(xiàn)

@Component
public class ConsolePrint implements IPrint {

    @Override
    public void print(String msg) {
        System.out.println("console print: " + msg);
    }
}

@Slf4j
@Component
public class LogPrint implements IPrint {
    @Override
    public void print(String msg) {
        log.info("log print: {}", msg);
    }
}

下面就是我們一般的引用方式

@Autowired注解時(shí),屬性名即為默認(rèn)的Bean名,如下面的logPrint就是獲取beanName=logPrint的bean
@Resource(name=xxx) 直接指定Bean的name,來唯一選擇匹配的bean

@Component
public class NormalPrintDemo {
    @Resource(name = "consolePrint")
    private IPrint consolePrint;

    @Autowired
    private IPrint logPrint;

    @PostConstruct
    public void init() {
        consolePrint.print(" console print!!!");
        logPrint.print(" log print!!!");
    }
}

上面是兩種常見的使用姿勢,此外還可以借助@Primary注解來聲明默認(rèn)的注入bean

  1. @Primary注解
    這個(gè)注解就是為了解決當(dāng)有多個(gè)bean滿足注入條件時(shí),有這個(gè)注解的實(shí)例被選中

根據(jù)上面的作用說明,很明顯可以得知一點(diǎn)

@Primary注解的使用有唯一性要求:即對應(yīng)上面的case,一個(gè)接口的子類中,只能有一個(gè)實(shí)現(xiàn)上有這個(gè)注解

假設(shè)將這個(gè)注解放在LogPrint上之后,如下

@Slf4j
@Component
@Primary
public class LogPrint implements IPrint {
    @Override
    public void print(String msg) {
        log.info("log print: {}", msg);
    }
}

結(jié)合上面的常用姿勢,加上這個(gè)注解之后,我們的測試用例應(yīng)該至少包含下面幾個(gè)

@Resource 指定beanName的是否會被@Primary影響
前面的@Autowired注解 + 屬性名的方式,是按照第一節(jié)的方式選擇呢,還是選擇被@Primary標(biāo)識的實(shí)例
@Autowired + 隨意的一個(gè)非beanName的屬性,驗(yàn)證是否會選中@Primary標(biāo)識的注解

@Component
public class PrintDemoBean {

    @Resource(name = "logPrint")
    private IPrint print;

    /**
     * 下面的注解不指定name,則實(shí)例為logPrint
     */
    @Autowired
    private IPrint consolePrint;

    // logPrint的選擇,由@Primary注解決定
    @Autowired
    private IPrint logPrint;

    // logPrint的選擇,由@Primary注解決定
    @Autowired(required = false)
    private IPrint xxxPrint;

    @PostConstruct
    public void init() {
        print.print("expect logPrint for [print]");
        consolePrint.print(" expect logPrint for [consolePrint]");
        logPrint.print("expect logPrint for [logPrint]");
        xxxPrint.print("expect logPrint for [xxxPrint]");
    }
}

執(zhí)行結(jié)果如下

2018-10-22 19:42:40.234  INFO 61966 --- [           main] c.g.h.b.b.choose.sameclz.LogPrint        : log print: expect logPrint for [print]
2018-10-22 19:42:40.235  INFO 61966 --- [           main] c.g.h.b.b.choose.sameclz.LogPrint        : log print:  expect consolePrint for [consolePrint]
2018-10-22 19:42:40.235  INFO 61966 --- [           main] c.g.h.b.b.choose.sameclz.LogPrint        : log print: expect logPrint for [logPrint]
2018-10-22 19:42:40.235  INFO 61966 --- [           main] c.g.h.b.b.choose.sameclz.LogPrint        : log print: expect logPrint for [xxxPrint]
  1. 小結(jié)
    根據(jù)前面的執(zhí)行,因此可以知曉,選擇bean的方式如下

存在@Primary注解時(shí)

@Resource注解指定name時(shí),根據(jù)name來查找對應(yīng)的bean@Autowired注解,全部都用@Primary標(biāo)識的注解
br/>@Autowired注解,全部都用@Primary標(biāo)識的注解
不存在@Primary注解時(shí)

@Resource注解指定name時(shí),根據(jù)name來查找對應(yīng)的bean@Autowired注解時(shí),根據(jù)屬性名去查對應(yīng)的Bean,如果查不到則拋異常;如果查到,那即是它了
br/>@Autowired注解時(shí),根據(jù)屬性名去查對應(yīng)的Bean,如果查不到則拋異常;如果查到,那即是它了
在我們實(shí)際的業(yè)務(wù)開發(fā)中,有多個(gè)bean名為xxx的異常應(yīng)該算是比較常見的,也就是說應(yīng)該不能有兩個(gè)bean叫同一個(gè)name;但考慮下下面這個(gè)場景

A的服務(wù),依賴B和C的服務(wù);而B和C是兩個(gè)完全獨(dú)立的第三方服務(wù),他們各自都提供了一個(gè)beanName=xxxService的bean,對于A而言,Spring容器中就會有BeanName沖突的問題了,而且這種場景,對A而言,也是不可控的啊,這種情況下改怎么辦?

  1. 同名Bean
    先來個(gè)case演示下同名bean的情況,如下定義兩個(gè)bean,除了包路徑不一樣外,類名相同,通過@Component注解方式聲明bean,因此兩個(gè)bean的beanName都是SameA
    
    package com.git.hui.boot.beanorder.choose.samename.a;

import org.springframework.stereotype.Component;

/**

  • Created by @author yihui in 21:32 18/10/22.*/
    @Component
    br/>*/
    @Component
    private String text ;
    public SameA() {
    text = "a sameA!";
    }

    public void print() {
    System.out.println(text);
    }
    }

package com.git.hui.boot.beanorder.choose.samename.b;

import org.springframework.stereotype.Component;

/**

  • Created by @author yihui in 21:33 18/10/22.*/
    @Component
    br/>*/
    @Component
    private String text;

    public SameA() {
    text = "B SameA";
    }

    public void print() {
    System.out.println(text);
    }
    }

    接下來測試下引用,是否有問題

    package com.git.hui.boot.beanorder.choose.samename;

import com.git.hui.boot.beanorder.choose.samename.a.SameA;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**

  • Created by @author yihui in 21:32 18/10/22.*/
    @Component
    br/>*/
    @Component

    @Autowired
    private SameA sameA;

    @PostConstruct
    public void init() {
    sameA.print();
    }
    }

執(zhí)行之后,毫不意外的拋出了異常,堆棧信息如下

org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [com.git.hui.boot.beanorder.Application]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'sameA' for bean class [com.git.hui.boot.beanorder.choose.samename.b.SameA] conflicts with existing, non-compatible bean definition of same name and class [com.git.hui.boot.beanorder.choose.samename.a.SameA]
    at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:184) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:316) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:233) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:271) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:91) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:694) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:398) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:330) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1258) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
    at com.git.hui.boot.beanorder.Application.main(Application.java:15) [classes/:na]
Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'sameA' for bean class [com.git.hui.boot.beanorder.choose.samename.b.SameA] conflicts with existing, non-compatible bean definition of same name and class [com.git.hui.boot.beanorder.choose.samename.a.SameA]
    at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:348) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:286) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:132) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:288) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:245) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:202) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:170) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    ... 12 common frames omitted
  1. 同名問題規(guī)避
    如果真的出現(xiàn)了上面這個(gè)問題,該怎么解決呢?如果這些bean是我們可控的,最簡單的方式就是不要同名,定義的時(shí)候指定beanName,如下
@Component("aSameA")
public class SameA {
    private String text ;
    public SameA() {
        text = "a sameA!";
    }

    public void print() {
        System.out.println(text);
    }
}

如果完全不可控呢?正如前面說的兩個(gè)第三方服務(wù)我都得依賴,但是他們有同名的bean,怎么破?

一個(gè)解決方法就是排除掉其中一個(gè)同名的bean的自動(dòng)加載,采用主動(dòng)注冊的方式注冊這個(gè)bean

排除自動(dòng)掃描的bean的方式如下,在啟動(dòng)類添加注解@ComponentScan并指定其中的excludeFilters屬性

@SpringBootApplication
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SameA.class)})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

然后自定義一個(gè)bean的配置類

package com.git.hui.boot.beanorder.choose.samename;

import com.git.hui.boot.beanorder.choose.samename.a.SameA;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Created by @author yihui in 22:14 18/10/22.
 */
@Configuration
public class AutoConfig {
    @Bean
    public SameA mySameA() {
        return new SameA();
    }
}

其他的代碼和之前沒有區(qū)別,再次執(zhí)行,結(jié)果如下, 最后的輸出為 a sameA!,根據(jù)類型來選擇了實(shí)例化的bean了
SpringBoot基礎(chǔ)篇之重名Bean的解決與多實(shí)例選擇

覺得不錯(cuò)請點(diǎn)贊支持,歡迎留言或進(jìn)我的個(gè)人群855801563領(lǐng)取【架構(gòu)資料專題目合集90期】、【BATJTMD大廠JAVA面試真題1000+】,本群專用于學(xué)習(xí)交流技術(shù)、分享面試機(jī)會,拒絕廣告,我也會在群內(nèi)不定期答題、探討。

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

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

AI