<譯>使用Spring 進(jìn)行自動(dòng)裝配:@Autowired, @Resource 和 @Inject注解詳解

本文翻譯自baeldung的文章《Wiring in Spring: @Autowired, @Resource and @Inject》,文中的引用和括號(hào)內(nèi)的斜線內(nèi)容為譯者自己的理解和感悟麦向,僅供參考

1. 概述

這篇關(guān)于Spring Framework的文章將為你演示三種與依賴注入相關(guān)的注解的使用瘟裸,它們分別是:@Resource, @Inject, 和 @Autowired。這些注解為你的類提供了通過聲明解決依賴的方式诵竭,舉個(gè)例子:

@Autowired
ArbitraryClass arbObject;

而不是通過直接實(shí)例化對(duì)象的方式(通過顯式調(diào)用構(gòu)造方法)话告,比如:

ArbitraryClass arbObject = new ArbitraryClass();

三種注解的其中兩個(gè)分別屬于Java的擴(kuò)展包:javax.annotation.Resourcejavax.inject.Inject。(Java官方的擴(kuò)展包)而@Autowired注解來自于org.springframework.beans.factory.annotation 包卵慰。(spring框架提供的注解包

三種注解都可以通過字段注入或setter方法注入的形式來解決依賴問題沙郭。在下面這個(gè)簡(jiǎn)單而實(shí)用的例子中,將基于三種注解各自的執(zhí)行方式來演示它們使用上的區(qū)別裳朋。

例子將借助集成測(cè)試專注于展示如何使用這三種注解病线,在測(cè)試中的依賴關(guān)系可以是任意的文件或任意的類。

文章示例中展示的僅為部分源碼鲤嫡,可在文末點(diǎn)擊GitHub鏈接前往查看和下載完整源碼

2. @Resource注解

@Resource注解是JSR-250中定義的注解集中的一員送挑,并且歸入了Java EE的規(guī)范包中。

JSR是由JCP(Java Community Process)定制的Java標(biāo)準(zhǔn)化技術(shù)規(guī)范泛范,可以看做是官方給開發(fā)者提供的接口標(biāo)準(zhǔn)让虐,很多廠商會(huì)對(duì)此進(jìn)行實(shí)現(xiàn)紊撕,但也有很多會(huì)定制自己的規(guī)范罢荡。

該注解的執(zhí)行過程的優(yōu)先級(jí)順序如下:

  1. 命名匹配(Match by Name)
  2. 類型匹配(Match by Type)
  3. 限定符匹配(Match by Qualifier)

這些執(zhí)行過程均可以適用于setter方法注入字段注入

2.1 字段注入

字段注入來解決依賴是通過在實(shí)例字段上使用@Resource注解的方式進(jìn)行的对扶。

2.1.1 命名匹配

下面的集成測(cè)試案例演示了如何通過命名匹配的形式進(jìn)行字段注入:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestResourceNameType.class)
public class FieldResourceInjectionTest {

    @Resource(name="namedFile")
    private File defaultFile;
     
    @Test
    public void givenResourceAnnotation_WhenOnField_ThenDependencyValid(){
        assertNotNull(defaultFile);
        assertEquals("namedFile.txt", defaultFile.getName());
    }
}

我們一起瀏覽這段代碼区赵。在FieldResourceInjectionTest單元測(cè)試類中的第7行,在@Resource注解的屬性中傳入了bean的名稱實(shí)現(xiàn)了通過命名來解決依賴:

@Resource(name="namedFile")
private File defaultFile;

這個(gè)配置將使用“命名匹配”規(guī)則來控制依賴關(guān)系浪南。被命名為namedFile的bean必須定義在ApplicationContextTestResourceNameType這個(gè)應(yīng)用容器中笼才。(因?yàn)槭纠械募蓽y(cè)試指定加載了該容器類

請(qǐng)注意bean的id(命名)必須和對(duì)應(yīng)引用的屬性值相匹配**:

@Configuration
public class ApplicationContextTestResourceNameType {
 
    @Bean(name="namedFile")
    public File namedFile() {
        File namedFile = new File("namedFile.txt");
        return namedFile;
    }
}

在容器中錯(cuò)誤地定義bean將會(huì)導(dǎo)致一個(gè)org.springframework.beans.factory.NoSuchBeanDefinitionException異常的拋出络凿。在ApplicationContextTestResourceNameType應(yīng)用容器中的@Bean注里改變屬性值骡送,或者在FieldResourceInjectionTest集成測(cè)試類的@Resource注解里改變屬性值,將很容易證明出這一點(diǎn)絮记。

2.1.2 類型匹配

想演示通過類型匹配的執(zhí)行方式摔踱,只需要移除在FieldResourceInjectionTest 集成測(cè)試類里第7行的屬性,就像下面這樣:

@Resource
private File defaultFile;

然后再次運(yùn)行它怨愤。

這個(gè)測(cè)試依然將會(huì)通過派敷,因?yàn)楫?dāng)@Resource注解沒有接受到屬性值傳入的指定bean名稱時(shí),Spring Framework將會(huì)通過下一優(yōu)先級(jí)即類型匹配的方式來解決依賴注入。

2.1.3 限定符匹配

下面要演示限定符匹配的執(zhí)行方式篮愉,那么原來的集成測(cè)試場(chǎng)景將要做一些改變:定義兩個(gè)bean在ApplicationContextTestResourceQualifier應(yīng)用容器中:

@Configuration
public class ApplicationContextTestResourceQualifier {
 
    @Bean(name="defaultFile")
    public File defaultFile() {
        File defaultFile = new File("defaultFile.txt");
        return defaultFile;
    }
 
    @Bean(name="namedFile")
    public File namedFile() {
        File namedFile = new File("namedFile.txt");
        return namedFile;
    }
}

QualifierResourceInjectionTest集成測(cè)試類將演示限定符匹配的依賴處理過程腐芍。在這個(gè)場(chǎng)景中,需要向每個(gè)引用變量注入特定的bean依賴關(guān)系试躏。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestResourceQualifier.class)
public class QualifierResourceInjectionTest {
 
    @Resource
    private File dependency1;
     
    @Resource
    private File dependency2;
 
    @Test
    public void givenResourceAnnotation_WhenField_ThenDependency1Valid(){
        assertNotNull(dependency1);
        assertEquals("defaultFile.txt", dependency1.getName());
    }
 
    @Test
    public void givenResourceQualifier_WhenField_ThenDependency2Valid(){
        assertNotNull(dependency2);
        assertEquals("namedFile.txt", dependency2.getName());
    }
}

運(yùn)行這個(gè)集成測(cè)試猪勇,將會(huì)有一個(gè)org.springframework.beans.factory.NoUniqueBeanDefinitionException異常拋出。這個(gè)異常是由于應(yīng)用容器發(fā)現(xiàn)了兩個(gè)類型為File的bean的定義颠蕴,因而無法確定哪個(gè)bean應(yīng)該被用來解決依賴埠对。

要解決這個(gè)問題,請(qǐng)回到QualifierResourceInjectionTest單元測(cè)試類的第7到第10行:

@Resource
private File dependency1;
 
@Resource
private File dependency2;

然后添加以下兩行代碼:

@Qualifier("defaultFile")
 
@Qualifier("namedFile")

然后代碼塊將會(huì)像下面這樣:

@Resource
@Qualifier("defaultFile")
private File dependency1;
 
@Resource
@Qualifier("namedFile")
private File dependency2;

再次運(yùn)行一輪集成測(cè)試將會(huì)順利通過裁替。這個(gè)測(cè)試很客觀地演示了當(dāng)多個(gè)bean在容器中定義時(shí)项玛,@Qualifier注解通過指定依賴注入bean清除了未明確的困惑。

2.2 Setter方法注入

在字段上注入依賴項(xiàng)執(zhí)行過程適用于使用基于setter方法的注入弱判。

2.2.1 命名匹配

與上面的例子的區(qū)別是襟沮,MethodResourceInjectionTest集成測(cè)試中有一個(gè)setter方法:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestResourceNameType.class)
public class MethodResourceInjectionTest {
 
    private File defaultFile;
 
    @Resource(name="namedFile")
    protected void setDefaultFile(File defaultFile) {
        this.defaultFile = defaultFile;
    }
 
    @Test
    public void givenResourceAnnotation_WhenSetter_ThenDependencyValid(){
        assertNotNull(defaultFile);
        assertEquals("namedFile.txt", defaultFile.getName());
    }
}

通過setter方法注入來解決依賴關(guān)系是通過在引用變量的相應(yīng)setter方法上增加注解來完成的。將bean依賴項(xiàng)的名稱作為屬性值傳遞給@Resource注解:

private File defaultFile;
 
@Resource(name="namedFile")
protected void setDefaultFile(File defaultFile) {
    this.defaultFile = defaultFile;
}

在這個(gè)例子中將再次使用namedFile這個(gè)bean依賴昌腰。bean的名稱和對(duì)應(yīng)的屬性值必須匹配开伏。

按原樣再次運(yùn)行集成測(cè)試,它將順利通過遭商。

想要看到依賴關(guān)系確實(shí)是通過按名稱匹配的方式處理的固灵,可以修改@Resource注解的屬性值為你選擇的值,然后再次運(yùn)行測(cè)試劫流。這一次巫玻,測(cè)試將會(huì)拋出NoSuchBeanDefinitionException而失敗。

2.2.2 類型匹配

為了演示基于setter方法的類型匹配執(zhí)行過程祠汇,我們將使用MethodByTypeResourceTest集成測(cè)試:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestResourceNameType.class)
public class MethodByTypeResourceTest {
 
    private File defaultFile;
 
    @Resource
    protected void setDefaultFile(File defaultFile) {
        this.defaultFile = defaultFile;
    }
 
    @Test
    public void givenResourceAnnotation_WhenSetter_ThenValidDependency(){
        assertNotNull(defaultFile);
        assertEquals("namedFile.txt", defaultFile.getName());
    }
}

按原樣運(yùn)行測(cè)試仍秤,它將順利通過。

為了驗(yàn)證File依賴項(xiàng)是否確實(shí)由類型匹配的執(zhí)行過程解析可很,將defaultFile變量的類的類型修改為另一個(gè)類類型诗力,例如String。再次執(zhí)行MethodByTypeResourceTest集成測(cè)試我抠,這次將拋出NoSuchBeanDefinitionException苇本。

這個(gè)異常異常驗(yàn)證了該過程確實(shí)使用了類型匹配的規(guī)則來解析File依賴項(xiàng)。NoSuchBeanDefinitionException異常的拋出確認(rèn)了引用變量的名稱不需要匹配bean的名字菜拓。與之相反瓣窄,依賴項(xiàng)解析依賴是通過bean的類型與引用變量的類型進(jìn)行匹配。

2.2.3 限定符匹配

下面這個(gè)MethodByQualifierResourceTest集成測(cè)試將被用來演示限定符匹配的執(zhí)行過程:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestResourceQualifier.class)
public class MethodByQualifierResourceTest {
 
    private File arbDependency;
    private File anotherArbDependency;
 
    @Test
    public void givenResourceQualifier_WhenSetter_ThenValidDependencies(){
      assertNotNull(arbDependency);
        assertEquals("namedFile.txt", arbDependency.getName());
        assertNotNull(anotherArbDependency);
        assertEquals("defaultFile.txt", anotherArbDependency.getName());
    }
 
    @Resource
    @Qualifier("namedFile")
    public void setArbDependency(File arbDependency) {
        this.arbDependency = arbDependency;
    }
 
    @Resource
    @Qualifier("defaultFile")
    public void setAnotherArbDependency(File anotherArbDependency) {
        this.anotherArbDependency = anotherArbDependency;
    }
}

這個(gè)測(cè)試的目的是演示即使在應(yīng)用容器中定義了特定類型的多個(gè)bean實(shí)現(xiàn)尘惧,也可以通過@Qualifier注解與@Resource注解一起使用來解析依賴關(guān)系康栈。

與基于字段注入的依賴項(xiàng)處理類似,如果在應(yīng)用容器中定義了多個(gè)bean,而沒有使用@Qualifier注解指定應(yīng)使用哪個(gè)bean來注入依賴項(xiàng)啥么,那么將會(huì)拋出NoUniqueBeanDefinitionException異常登舞。

3. @Inject注解

@Inject注解歸屬于JSR-330注解集。這個(gè)注解具有以下按優(yōu)先級(jí)的執(zhí)行過程:

  1. 類型匹配(Match by Type)
  2. 限定符匹配(Match by Qualifier)
  3. 命名匹配(Match by Name)

這些執(zhí)行過程適用于Setter方法注入和字段注入悬荣。為了使用@Inject注解菠秒,需要將javax.inject庫通過Gradle或Maven進(jìn)行依賴包引入。

使用Gradle:

testCompile group: 'javax.inject', name: 'javax.inject', version: '1'

使用Maven:

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

3.1 字段注入

3.1.1 類型匹配

集成測(cè)試中的示例將被修改為使用其它類型的依賴項(xiàng)氯迂,即ArbitraryDependency類践叠。該依賴項(xiàng)只是作為一個(gè)簡(jiǎn)單依賴類,并且不具備更多的意義嚼蚀。代碼如下:

@Component
public class ArbitraryDependency {
 
    private final String label = "Arbitrary Dependency";
 
    public String toString() {
        return label;
    }
}

這一例子中的集成測(cè)試如下:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestInjectType.class)
public class FieldInjectTest {
 
    @Inject
    private ArbitraryDependency fieldInjectDependency;
 
    @Test
    public void givenInjectAnnotation_WhenOnField_ThenValidDependency(){
        assertNotNull(fieldInjectDependency);
        assertEquals("Arbitrary Dependency",
          fieldInjectDependency.toString());
    }
}

不像@Resource注解優(yōu)先使用命名匹配規(guī)則禁灼,@Inject注解的默認(rèn)行為是優(yōu)先使用類型匹配規(guī)則來解決依賴。

這意味著即使一個(gè)類型依賴變量的命名與對(duì)容器中對(duì)應(yīng)定義著的bean命名不同轿曙,依賴依然會(huì)被正常處理弄捕。

成員變量的命名如下:

@Inject
private ArbitraryDependency fieldInjectDependency;

與容器中配置的bean的命名有所不同:

@Bean
public ArbitraryDependency injectDependency() {
    ArbitraryDependency injectDependency = new ArbitraryDependency();
    return injectDependency;
}

當(dāng)測(cè)試被運(yùn)行時(shí),這個(gè)依賴是可以被正常處理的导帝。

3.1.2 限定符匹配

如果一個(gè)特別的類有多個(gè)不同的實(shí)現(xiàn)并且一個(gè)確定的類需要一個(gè)指定的bean時(shí)守谓,又該如何處理呢?下面我們修改集成測(cè)試的例子來指定另一個(gè)依賴您单。

在下面的例子中斋荞,我們通過繼承在“類型匹配”例子中使用的ArbitraryDependency類創(chuàng)建了AnotherArbitraryDependency子類:

public class AnotherArbitraryDependency extends ArbitraryDependency {
 
    private final String label = "Another Arbitrary Dependency";
 
    public String toString() {
        return label;
    }
}

測(cè)試用例的每一項(xiàng)都是為了確保每個(gè)依賴項(xiàng)都正確地注入到每個(gè)引用變量中:

@Inject
private ArbitraryDependency defaultDependency;
 
@Inject
private ArbitraryDependency namedDependency;

用于演示按限定符匹配的FieldQualifierInjectTest集成測(cè)試如下:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestInjectQualifier.class)
public class FieldQualifierInjectTest {
 
    @Inject
    private ArbitraryDependency defaultDependency;
 
    @Inject
    private ArbitraryDependency namedDependency;
 
    @Test
    public void givenInjectQualifier_WhenOnField_ThenDefaultFileValid(){
        assertNotNull(defaultDependency);
        assertEquals("Arbitrary Dependency",
          defaultDependency.toString());
    }
 
    @Test
    public void givenInjectQualifier_WhenOnField_ThenNamedFileValid(){
        assertNotNull(defaultDependency);
        assertEquals("Another Arbitrary Dependency",
          namedDependency.toString());
    }
}

如果應(yīng)用容器中存在特定類的多個(gè)實(shí)現(xiàn),并且FieldQualifierInjectTest集成測(cè)試嘗試以下面列出的方式去注入依賴:

@Inject
private ArbitraryDependency defaultDependency;
 
@Inject
private ArbitraryDependency namedDependency;

那么將會(huì)拋出一個(gè)NoUniqueBeanDefinitionException異常虐秦。

拋出此異常是因?yàn)镾pring Framework指出某個(gè)類具有有多個(gè)實(shí)現(xiàn)平酿,并且對(duì)于使用哪個(gè)類產(chǎn)生困惑。為了闡明這種混淆的關(guān)系羡疗,請(qǐng)轉(zhuǎn)到FieldQualifierInjectTest集成測(cè)試的第7行和第10行:

@Inject
private ArbitraryDependency defaultDependency;
 
@Inject
private ArbitraryDependency namedDependency;

將所需要的bean名稱傳遞給@Qualifier注解染服,該注解與@Inject注解一起使用。代碼現(xiàn)在看起來像下面這樣:

@Inject
@Qualifier("defaultFile")
private ArbitraryDependency defaultDependency;
 
@Inject
@Qualifier("namedFile")
private ArbitraryDependency namedDependency;

@Qualifier注解在接收bean名稱時(shí)需要進(jìn)行嚴(yán)格匹配叨恨,以確保bean名稱被正確地傳遞給Qualifier,否則將會(huì)拋出NoUniqueBeanDefinitionException異常挖垛。再次運(yùn)行測(cè)試將會(huì)順利通過痒钝。

3.1.3 按名稱匹配

用于演示按名稱匹配的FieldByNameInjectTest集成測(cè)試類似于按類型匹配的方式。唯一的區(qū)別在于需要特定名稱的bean而不是特定的類型痢毒。在這個(gè)例子中送矩,我們?cè)俅卫^承ArbitraryDependency以創(chuàng)建YetAnotherArbitraryDependency子類:

public class YetAnotherArbitraryDependency extends ArbitraryDependency {
 
    private final String label = "Yet Another Arbitrary Dependency";
 
    public String toString() {
        return label;
    }
}

下面演示按名稱匹配的注入方式,我們使用以下集成測(cè)試:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestInjectName.class)
public class FieldByNameInjectTest {
 
    @Inject
    @Named("yetAnotherFieldInjectDependency")
    private ArbitraryDependency yetAnotherFieldInjectDependency;
 
    @Test
    public void givenInjectQualifier_WhenSetOnField_ThenDependencyValid(){
        assertNotNull(yetAnotherFieldInjectDependency);
        assertEquals("Yet Another Arbitrary Dependency",
          yetAnotherFieldInjectDependency.toString());
    }
}

應(yīng)用容器內(nèi)容如下:

@Configuration
public class ApplicationContextTestInjectName {
 
    @Bean
    public ArbitraryDependency yetAnotherFieldInjectDependency() {
        ArbitraryDependency yetAnotherFieldInjectDependency =
          new YetAnotherArbitraryDependency();
        return yetAnotherFieldInjectDependency;
    }
}

直接運(yùn)行集成測(cè)試哪替,它將順利通過栋荸。

為了驗(yàn)證依賴是否確實(shí)是按名稱匹配的方式進(jìn)行注入,可以傳遞給@Named不同于現(xiàn)在的名稱。再次運(yùn)行測(cè)試晌块,這次將會(huì)拋出NoSuchBeanDefinitionException異常爱沟。

以上例子可以看出@Named與@Qualifier注解的使用方式和作用基本相同,都是通過bean名稱來指定依賴匆背,區(qū)別在于@Named注解與@Inject注解同屬于JSR-330注解集呼伸,而@Qualifier是Spring Framework提供的注解(javax.inject包中同樣提供了一個(gè)名為@Qualifier的注解,但該注解的作用是用來標(biāo)識(shí)作為限定符注解钝尸,@Named注解即為官方的一個(gè)標(biāo)準(zhǔn)實(shí)現(xiàn))括享。使用的取舍在于你對(duì)Spring Framework依賴程度的期望。

3.2 Setter方法注入

@Inject注解的setter方法注入與@Resource注解相類似珍促。不再注釋引用變量铃辖,而是注釋相應(yīng)的setter方法≈硇穑基于字段的依賴注入方式也同樣適用于setter方法注入澳叉。

4. @Autowired 注解

@Autowired注解的注入過程類似于@Inject注釋。唯一的區(qū)別是@Autowired注解是Spring框架的一部分沐悦。

Spring 的官方文檔同樣也說明了這一點(diǎn)成洗,@Autowired與@Inject注解具有相同的依賴處理過程。

此注解與@Inject注解具有相同的注入過程優(yōu)先級(jí):

  1. 類型匹配(Match by Type)
  2. 限定符匹配(Match by Qualifier)
  3. 命名匹配(Match by Name)

這些注入過程同樣適用于setter方法注入和字段注入藏否。

4.1 字段注入

4.1.1 類型匹配

演示@Autowired注解按類型匹配的示例與演示@Inject注解按類型匹配的示例相類似瓶殃。下面這個(gè)FieldAutowiredTest集成測(cè)試將用來演示@Autowired注解進(jìn)行類型匹配的過程:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestAutowiredType.class)
public class FieldAutowiredTest {
 
    @Autowired
    private ArbitraryDependency fieldDependency;
 
    @Test
    public void givenAutowired_WhenSetOnField_ThenDependencyResolved() {
        assertNotNull(fieldDependency);
        assertEquals("Arbitrary Dependency", fieldDependency.toString());
    }
}

集成測(cè)試的應(yīng)用容器定義如下:

@Configuration
public class ApplicationContextTestAutowiredType {
 
    @Bean
    public ArbitraryDependency autowiredFieldDependency() {
        ArbitraryDependency autowiredFieldDependency =
          new ArbitraryDependency();
        return autowiredFieldDependency;
    }
}

這個(gè)集成測(cè)試的目的是為了證明類型匹配相比其他匹配規(guī)則具有最高的優(yōu)先級(jí)。注意FieldAutowiredTest類中的第8行的成員變量命名:

@Autowired
private ArbitraryDependency fieldDependency;

它與應(yīng)用容器中的bean名稱是不同的:

@Bean
public ArbitraryDependency autowiredFieldDependency() {
    ArbitraryDependency autowiredFieldDependency =
      new ArbitraryDependency();
    return autowiredFieldDependency;
}

而這個(gè)測(cè)試將順利運(yùn)行通過副签。

為了確認(rèn)通過按類型匹配過程確實(shí)注入了依賴遥椿,我們修改fieldDependency成員變量的類型并再次運(yùn)行測(cè)試。這一輪FieldAutowiredTest測(cè)試必定會(huì)失敗淆储,并拋出NoSuchBeanDefinitionException異常冠场。這驗(yàn)證了按類型匹配確實(shí)處理了依賴關(guān)系。

4.1.2 限定符匹配

如果遇到了應(yīng)用容器中定義了多個(gè)實(shí)現(xiàn)bean的情況:

@Configuration
public class ApplicationContextTestAutowiredQualifier {
 
    @Bean
    public ArbitraryDependency autowiredFieldDependency() {
        ArbitraryDependency autowiredFieldDependency =
          new ArbitraryDependency();
        return autowiredFieldDependency;
    }
 
    @Bean
    public ArbitraryDependency anotherAutowiredFieldDependency() {
        ArbitraryDependency anotherAutowiredFieldDependency =
          new AnotherArbitraryDependency();
        return anotherAutowiredFieldDependency;
    }
}

再執(zhí)行下面的集成測(cè)試:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestAutowiredQualifier.class)
public class FieldQualifierAutowiredTest {
 
    @Autowired
    private ArbitraryDependency fieldDependency1;
 
    @Autowired
    private ArbitraryDependency fieldDependency2;
 
    @Test
    public void givenAutowiredQualifier_WhenOnField_ThenDep1Valid(){
        assertNotNull(fieldDependency1);
        assertEquals("Arbitrary Dependency", fieldDependency1.toString());
    }
 
    @Test
    public void givenAutowiredQualifier_WhenOnField_ThenDep2Valid(){
        assertNotNull(fieldDependency2);
        assertEquals("Another Arbitrary Dependency",
          fieldDependency2.toString());
    }
}

將會(huì)拋出NoUniqueBeanDefinitionException

這個(gè)異常是由容器中定義了兩個(gè)bean引起的歧義導(dǎo)致的本砰。Spring Framework容器不知道哪個(gè)bean依賴應(yīng)該注入到哪個(gè)引用變量上碴裙。通過將@Qualifier注解添加到FieldQualifierAutowiredTest中的第7行和第10行將解決該問題:

@Autowired
private FieldDependency fieldDependency1;
 
@Autowired
private FieldDependency fieldDependency2;

代碼塊看起來將會(huì)像下面這樣:

@Autowired
@Qualifier("autowiredFieldDependency")
private FieldDependency fieldDependency1;
 
@Autowired
@Qualifier("anotherAutowiredFieldDependency")
private FieldDependency fieldDependency2;

再次運(yùn)行將順利通過測(cè)試。

4.1.3 按名稱匹配

我們使用相同的集成測(cè)試場(chǎng)景來演示@Autowired注解如何通過名稱匹配注入字段依賴關(guān)系点额。當(dāng)通過名稱自動(dòng)注入時(shí)舔株,需要在ApplicationContextTestAutowiredName應(yīng)用容器上使用@ComponentScan注解:

@Configuration
@ComponentScan(basePackages={"com.baeldung.dependency"})
    public class ApplicationContextTestAutowiredName {
}

@ComponentScan用來標(biāo)注在哪些包中搜索帶@Component注解的Java類。例如还棱,應(yīng)用程序?qū)呙?em>com.baeldung.dependency包中使用@Component注解的類载慈。在現(xiàn)在的場(chǎng)景中,ArbitraryDependency 類由于使用了@Component注解珍手,它將會(huì)被Spring Framework檢查到:

@Component(value="autowiredFieldDependency")
public class ArbitraryDependency {
 
    private final String label = "Arbitrary Dependency";
 
    public String toString() {
        return label;
    }
}

賦給@Component注解的value屬性值autowiredFieldDependency將告訴Spring Framework老虫,ArbitraryDependency類是一個(gè)名為autowiredFieldDependency的Bean。為了使@Autowired按名稱解析依賴露懒,組件名稱必須與FieldAutowiredNameTest測(cè)試中定義的字段名稱相對(duì)應(yīng);參考第8行:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestAutowiredName.class)
public class FieldAutowiredNameTest {
 
    @Autowired
    private ArbitraryDependency autowiredFieldDependency;
 
    @Test
    public void givenAutowiredAnnotation_WhenOnField_ThenDepValid(){
        assertNotNull(autowiredFieldDependency);
        assertEquals("Arbitrary Dependency",
          autowiredFieldDependency.toString());
    }
}

運(yùn)行集成測(cè)試秤茅,它將順利通過。

但是我們?cè)趺粗?em>@Autowired注解確實(shí)使用了按名稱匹配的方式呢晒杈?將成員變量autowiredFieldDependency的名稱更改為另一個(gè)名稱嫂伞,再次運(yùn)行測(cè)試。

這一次拯钻,測(cè)試將執(zhí)行失敗并拋出NoUniqueBeanDefinitionException帖努。類似的檢查方式是將@Component的value值autowiredFieldDependency修改為另一個(gè)值并再次運(yùn)行測(cè)試。同樣將拋出NoUniqueBeanDefinitionException粪般。

這個(gè)異常證明了如果使用了錯(cuò)誤的bean名稱拼余,將不會(huì)找到有效的bean。因此亩歹, @Autowired注解使用了按名稱匹配的方式匙监。

4.2 Setter方法注入

@Autowired注解的setter方法注入與基于@Resource注解的注入方式類似⌒∽鳎基于字段的注入過程同樣適用于基于setter方法注入亭姥。

5.應(yīng)用這些注解

現(xiàn)在出現(xiàn)了一個(gè)問題,應(yīng)該在什么情況下應(yīng)用這些注解顾稀?問題的答案在于應(yīng)用程序所使用的設(shè)計(jì)方案以及開發(fā)者期望如何基于注解不同的注入方式來利用多態(tài)性达罗。

5.1 通過多態(tài)在應(yīng)用程序范圍內(nèi)使用單例

如果使用多態(tài)只是基于接口和抽象類來實(shí)現(xiàn)應(yīng)用程序行為,并且這些行為在整個(gè)應(yīng)用程序中使用静秆,則應(yīng)該使用@Inject@Autowired注解粮揉。

這種方法的好處將在應(yīng)用程序升級(jí)或需要補(bǔ)丁來修復(fù)錯(cuò)誤時(shí)體現(xiàn)出來;其次在對(duì)類進(jìn)行更換時(shí)可以對(duì)應(yīng)用程序造成最小的負(fù)面影響抚笔。在此方案中扶认,處理依賴的主要方式是按類型匹配;

5.2 通過多態(tài)細(xì)粒度配置應(yīng)用程序行為

如果多態(tài)的使用是為了使應(yīng)用程序擁有更復(fù)雜的行為——每一個(gè)行為都基于不同的接口或抽象類殊橙,并且使用它們的實(shí)現(xiàn)會(huì)因應(yīng)用程序而異辐宾,則使用@Resource注解。在此方案中蛀柴,處理依賴的主要方式是按名稱匹配螃概。

5.3 在Java EE平臺(tái)處理依賴注入

如果選擇在JavaEE平臺(tái)而不是Spring處理所有的依賴,那么可以在@Resource注解和@Inject注解中選擇其一鸽疾。你應(yīng)該基于它們的默認(rèn)處理方式縮小兩種注解之間的最終使用范圍。

5.4 在Spring框架中處理依賴注入

如果應(yīng)用僅僅依賴于Spring框架训貌,那唯一選擇就是@Autowired注解制肮。

5.5 總結(jié)

下表對(duì)以上討論進(jìn)行了總結(jié):

場(chǎng)景 @Resource @Inject @Autowired
通過多態(tài)在應(yīng)用程序范圍使用單例 ×
通過多態(tài)細(xì)粒度配置應(yīng)用程序行為 × ×
依賴注入僅由使用Java EE平臺(tái) ×
依賴注入僅使用Spring Framework × ×

6. 結(jié)論

本文旨在更深入地了解每個(gè)注解的處理過程冒窍。了解每個(gè)注解的注入規(guī)則將有助于更好地進(jìn)行整體應(yīng)用的設(shè)計(jì)和維護(hù)。

文中使用的代碼均可以在GitHub中找到豺鼻。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末综液,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子儒飒,更是在濱河造成了極大的恐慌谬莹,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桩了,死亡現(xiàn)場(chǎng)離奇詭異附帽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)井誉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門蕉扮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人颗圣,你說我怎么就攤上這事喳钟。” “怎么了在岂?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵奔则,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我蔽午,道長(zhǎng)易茬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任祠丝,我火速辦了婚禮疾呻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘写半。我一直安慰自己岸蜗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布叠蝇。 她就那樣靜靜地躺著璃岳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悔捶。 梳的紋絲不亂的頭發(fā)上铃慷,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音蜕该,去河邊找鬼犁柜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛堂淡,可吹牛的內(nèi)容都是我干的馋缅。 我是一名探鬼主播扒腕,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼萤悴!你這毒婦竟也來了瘾腰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤覆履,失蹤者是張志新(化名)和其女友劉穎蹋盆,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硝全,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡栖雾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了柳沙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岩灭。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖赂鲤,靈堂內(nèi)的尸體忽然破棺而出噪径,到底是詐尸還是另有隱情,我是刑警寧澤数初,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布找爱,位于F島的核電站,受9級(jí)特大地震影響泡孩,放射性物質(zhì)發(fā)生泄漏车摄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一仑鸥、第九天 我趴在偏房一處隱蔽的房頂上張望吮播。 院中可真熱鬧,春花似錦眼俊、人聲如沸意狠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽环戈。三九已至,卻和暖如春澎灸,著一層夾襖步出監(jiān)牢的瞬間院塞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工性昭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拦止,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓糜颠,卻偏偏與公主長(zhǎng)得像创泄,于是被迫代替她去往敵國(guó)和親艺玲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子括蝠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1.1 spring IoC容器和beans的簡(jiǎn)介 Spring 框架的最核心基礎(chǔ)的功能是IoC(控制反轉(zhuǎn))容器鞠抑,...
    simoscode閱讀 6,701評(píng)論 2 22
  • 1.1 Spring IoC容器和bean簡(jiǎn)介 本章介紹了Spring Framework實(shí)現(xiàn)的控制反轉(zhuǎn)(IoC)...
    起名真是難閱讀 2,567評(píng)論 0 8
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)忌警,斷路器搁拙,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • 本來是準(zhǔn)備看一看Spring源碼的。然后在知乎上看到來一個(gè)帖子法绵,說有一群**自己連Spring官方文檔都沒有完全讀...
    此魚不得水閱讀 6,926評(píng)論 4 21
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,748評(píng)論 6 342