【Spring註解驅動開發】使用@Autowired@Qualifier@Primary三大註解自動裝配元件,你會了嗎?

冰河團隊發表於2020-08-12

寫在前面

【Spring專題】停更一個多月,期間在更新其他專題的內容,不少小夥伴紛紛留言說:冰河,你【Spring專題】是不是停更了啊!其實並沒有停更,只是中途有很多小夥伴留言說急需學習一些知識技能,以便於跳槽,哈哈,大家都懂得!所以,中途停更了一段時間,寫了一些其他專題的文章。現在,繼續更新【String專題】。

關注 冰河技術 微信公眾號,訂閱更多技術乾貨!如果文章對你有所幫助,請不要吝惜你的點贊、在看、留言和轉發,你的支援是我持續創作的最大動力!

專案工程原始碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

註解說明

@Autowired註解

@Autowired 註解,可以對類成員變數、方法和建構函式進行標註,完成自動裝配的工作。@Autowired 註解可以放在類,介面以及方法上。在使用@Autowired之前,我們對一個bean配置屬性時,是用如下xml檔案的形式進行配置的。

<property name="屬性名" value=" 屬性值"/>

@Autowired 註解的原始碼如下所示。

package org.springframework.beans.factory.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
	boolean required() default true;
}

@Autowired 註解說明:

(1)預設優先按照型別去容器中找對應的元件,找到就賦值;

(2)如果找到多個相同型別的元件,再將屬性名稱作為元件的id,到 IOC 容器中進行查詢。

@Qualifier註解

@Autowired是根據型別進行自動裝配的,如果需要按名稱進行裝配,則需要配合@Qualifier 註解使用。

@Qualifier註解原始碼如下所示。

package org.springframework.beans.factory.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
	String value() default "";
}

@Primary註解

在Spring 中使用註解,常使用@Autowired, 預設是根據型別Type來自動注入的。但有些特殊情況,對同一個介面,可能會有幾種不同的實現類,而預設只會採取其中一種實現的情況下, 就可以使用@Primary註解來標註優先使用哪一個實現類。

@Primary註解的原始碼如下所示。

package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Primary {

}

自動裝配

在進行專案實戰之前,我們先來說說什麼是Spring元件的自動裝配。Spring元件的自動裝配就是:Spring利用依賴注入,也就是我們通常所說的DI,完成對IOC容器中各個元件的依賴關係賦值。

專案實戰

測試@Autowired註解

這裡,我們以之前專案中建立的dao、service和controller為例進行說明。dao、service和controller的初始程式碼分別如下所示。

  • dao
package io.mykit.spring.plugins.register.dao;
import org.springframework.stereotype.Repository;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試的dao
 */
@Repository
public class PersonDao {
}
  • service
package io.mykit.spring.plugins.register.service;
import io.mykit.spring.plugins.register.dao.PersonDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試的Service
 */
@Service
public class PersonService {
    @Autowired
    private PersonDao personDao;
}
  • controller
package io.mykit.spring.plugins.register.controller;
import org.springframework.stereotype.Controller;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試的controller
 */
@Controller
public class PersonController {
    @Autowired
    private PersonService personService;
}

可以看到,我們在Service中使用@Autowired註解注入了Dao,在Controller中使用@Autowired註解注入了Service。為了方便測試,我們在PersonService類中生成一個toString()方法,如下所示。

package io.mykit.spring.plugins.register.service;
import io.mykit.spring.plugins.register.dao.PersonDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試的Service
 */
@Service
public class PersonService {
    @Autowired
    private PersonDao personDao;

    @Override
    public String toString() {
        return personDao.toString();
    }
}

這裡,我們在PersonService類的toString()方法中直接呼叫personDao的toString()方法並返回。為了更好的演示效果,我們在專案的 io.mykit.spring.plugins.register.config 包下建立AutowiredConfig類,如下所示。

package io.mykit.spring.plugins.register.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試自動裝配元件的Config配置類
 */
@Configuration
@ComponentScan(value = {
        "io.mykit.spring.plugins.register.dao", 
        "io.mykit.spring.plugins.register.service", 
        "io.mykit.spring.plugins.register.controller"})
public class AutowiredConfig {

}

接下來,我們來測試一下上面的程式,我們在專案的src/test/java目錄下的 io.mykit.spring.test 包下建立AutowiredTest類,如下所示。

package io.mykit.spring.test;
import io.mykit.spring.plugins.register.config.AutowiredConfig;
import io.mykit.spring.plugins.register.service.PersonService;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試自動裝配
 */
public class AutowiredTest {
    @Test
    public void testAutowired01(){
        //建立IOC容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
        PersonService personService = context.getBean(PersonService.class);
        System.out.println(personService);
        context.close();
    }
}

測試方法比較簡單,這裡,我就不做過多說明了。接下來,我們執行AutowiredTest類的testAutowired01()方法,得出的輸出結果資訊如下所示。

io.mykit.spring.plugins.register.dao.PersonDao@10e92f8f

可以看到,輸出了PersonDao資訊。

那麼問題來了:我們在PersonService類中輸出的PersonDao,和我們直接在Spring IOC容器中獲取的PersonDao是不是同一個物件呢?

我們可以在AutowiredTest類的testAutowired01()方法中新增獲取PersonDao物件的方法,並輸出獲取到的PersonDao物件,如下所示。

@Test
public void testAutowired01(){
    //建立IOC容器
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
    PersonService personService = context.getBean(PersonService.class);
    System.out.println(personService);
    PersonDao personDao = context.getBean(PersonDao.class);
    System.out.println(personDao);
    context.close();
}

我們再次執行AutowiredTest類的testAutowired01()方法,輸出的結果資訊如下所示。

io.mykit.spring.plugins.register.dao.PersonDao@10e92f8f
io.mykit.spring.plugins.register.dao.PersonDao@10e92f8f

可以看到,我們在PersonService類中輸出的PersonDao物件和直接從IOC容器中獲取的PersonDao物件是同一個物件。

如果在Spring容器中存在對多個PersonDao物件該如何處理呢?

首先,為了更加直觀的看到我們使用@Autowired註解裝配的是哪個PersonDao物件,我們對PersonDao類進行改造,為其加上一個remark欄位,為其賦一個預設值,如下所示。

package io.mykit.spring.plugins.register.dao;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Repository;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試的dao
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Repository
public class PersonDao {
    private String remark = "1";
}

接下來,我們就在AutowiredConfig類中注入一個PersonDao物件,並且顯示指定PersonDao物件在IOC容器中的bean的名稱為personDao2,併為PersonDao物件的remark欄位賦值為2,如下所示。

  @Bean("personDao2")
  public PersonDao personDao(){
      return new PersonDao("2");
  }

目前,在我們的IOC容器中就會注入兩個PersonDao物件。那此時,@Autowired註解裝配的是哪個PersonDao物件呢?

接下來,我們執行AutowiredTest類的testAutowired01()方法,輸出的結果資訊如下所示。

PersonDao{remark='1'}

可以看到,結果資訊輸出了1,說明:@Autowired註解預設優先按照型別去容器中找對應的元件,找到就賦值;如果找到多個相同型別的元件,再將屬性名稱作為元件的id,到 IOC 容器中進行查詢。

那我們如何讓@Autowired裝配personDao2呢? 這個問題問的好,其實很簡單,我們將PersonService類中的personDao全部修改為personDao2,如下所示。

package io.mykit.spring.plugins.register.service;
import io.mykit.spring.plugins.register.dao.PersonDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試的Service
 */
@Service
public class PersonService {
    @Autowired
    private PersonDao personDao2;
    @Override
    public String toString() {
        return personDao2.toString();
    }
}

此時,我們再次執行AutowiredTest類的testAutowired01()方法,輸出的結果資訊如下所示。

PersonDao{remark='2'}

可以看到,此時命令列輸出了personDao2的資訊。

測試@Qualifier註解

從測試@Autowired註解的結果來看:@Autowired註解預設優先按照型別去容器中找對應的元件,找到就賦值;如果找到多個相同型別的元件,再將屬性名稱作為元件的id,到 IOC 容器中進行查詢。

如果IOC容器中存在多個相同型別的元件時,我們可不可以顯示指定@Autowired註解裝配哪個元件呢?有些小夥伴肯定會說:廢話!你都這麼問了,那肯定可以啊!沒錯,確實可以啊!此時,@Qualifier註解就派上用場了!

在之前的測試案例中,命令列輸出了 PersonDao{remark='2'} 說明@Autowired註解裝配了personDao2,那我們如何顯示的讓@Autowired註解裝配personDao呢?

比較簡單,我們只需要在PersonService類上personDao2欄位上新增@Qualifier註解,顯示指定@Autowired註解裝配personDao,如下所示。

@Qualifier("personDao")
@Autowired
private PersonDao personDao2;

此時,我們再次執行AutowiredTest類的testAutowired01()方法,輸出的結果資訊如下所示。

PersonDao{remark='1'}

可以看到,此時儘管欄位的名稱為personDao2,但是我們使用了@Qualifier註解顯示指定@Autowired註解裝配personDao物件,所以,最終的結果輸出了personDao物件的資訊。

測試容器中無元件的情況

如果IOC容器中無相應的元件,會發生什麼情況呢?此時,我們刪除PersonDao類上的@Repository註解,並且刪除AutowiredConfig類中的personDao()方法上的@Bean註解,如下所示。

package io.mykit.spring.plugins.register.dao;

/**
 * @author binghe
 * @version 1.0.0
 * @description 測試的dao
 */
public class PersonDao {
    private String remark = "1";

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    @Override
    public String toString() {
        return "PersonDao{" +
                "remark='" + remark + '\'' +
                '}';
    }
}
package io.mykit.spring.plugins.register.config;

import io.mykit.spring.plugins.register.dao.PersonDao;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @author binghe
 * @version 1.0.0
 * @description 測試自動裝配元件的Config配置類
 */
@Configuration
@ComponentScan(value = {
        "io.mykit.spring.plugins.register.dao",
        "io.mykit.spring.plugins.register.service",
        "io.mykit.spring.plugins.register.controller"})
public class AutowiredConfig {
    public PersonDao personDao(){
        PersonDao personDao = new PersonDao();
        personDao.setRemark("2");
        return personDao;
    }
}

此時IOC容器中不再有personDao,我們再次執行AutowiredTest類的testAutowired01()方法,輸出的結果資訊如下所示。

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'io.mykit.spring.plugins.register.dao.PersonDao' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier(value=personDao), @org.springframework.beans.factory.annotation.Autowired(required=true)}

可以看到,Spring丟擲了異常,未找到相應的bean物件,我們能不能讓Spring不報錯呢? 那肯定可以啊!Spring的異常資訊中都給出了相應的提示。

{@org.springframework.beans.factory.annotation.Qualifier(value=personDao), @org.springframework.beans.factory.annotation.Autowired(required=true)}

解決方案就是在PersonService類的@Autowired新增一個屬性required=false,如下所示。

@Qualifier("personDao")
@Autowired(required = false)
private PersonDao personDao2;

並且我們修改下PersonService的toString()方法,如下所示。

@Override
public String toString() {
    return "PersonService{" +
        "personDao2=" + personDao2 +
        '}';
}

此時,還需要將AutowiredTest類的testAutowired01()方法中直接從IOC容器中獲取personDao的程式碼刪除,如下所示。

@Test
public void testAutowired01(){
    //建立IOC容器
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
    PersonService personService = context.getBean(PersonService.class);
    System.out.println(personService);
    context.close();
}

此時,我們再次執行AutowiredTest類的testAutowired01()方法,輸出的結果資訊如下所示。

PersonService{personDao2=null}

可以看到,當為@Autowired新增屬性required=false後,即使IOC容器中沒有對應的物件,Spring也不會丟擲異常。此時,裝配的物件就為null。

測試完成後,我們再次為PersonDao類新增@Repository註解,並且為AutowiredConfig類中的personDao()方法新增@Bean註解。

測試@Primary註解

在Spring中,對同一個介面,可能會有幾種不同的實現類,而預設只會採取其中一種實現的情況下, 就可以使用@Primary註解來標註優先使用哪一個實現類。

首先,我們在AutowiredConfig類的personDao()方法上新增@Primary註解,此時,我們需要刪除PersonService類中personDao欄位上的@Qualifier註解,這是因為@Qualifier註解為顯示指定裝配哪個元件,如果使用了@Qualifier註解,無論是否使用了@Primary註解,都會裝配@Qualifier註解標註的物件。

設定完成後,我們再次執行AutowiredTest類的testAutowired01()方法,輸出的結果資訊如下所示。

PersonService{personDao2=PersonDao{remark='2'}}

可以看到,此時remark的值為2,裝配了AutowiredConfig類中注入的personDao。

接下來,我們為PersonService類中personDao欄位再次新增@Qualifier註解,如下所示。

@Qualifier("personDao")
@Autowired(required = false)
private PersonDao personDao;

此時,我們再次執行AutowiredTest類的testAutowired01()方法,輸出的結果資訊如下所示。

PersonService{personDao=PersonDao{remark='1'}}

可以看到,此時,Spring裝配了使用@Qualifier標註的personDao。

重磅福利

關注「 冰河技術 」微信公眾號,後臺回覆 “設計模式” 關鍵字領取《深入淺出Java 23種設計模式》PDF文件。回覆“Java8”關鍵字領取《Java8新特性教程》PDF文件。回覆“限流”關鍵字獲取《億級流量下的分散式限流解決方案》PDF文件,三本PDF均是由冰河原創並整理的超硬核教程,面試必備!!

好了,今天就聊到這兒吧!別忘了點個贊,給個在看和轉發,讓更多的人看到,一起學習,一起進步!!

寫在最後

如果你覺得冰河寫的還不錯,請微信搜尋並關注「 冰河技術 」微信公眾號,跟冰河學習高併發、分散式、微服務、大資料、網際網路和雲原生技術,「 冰河技術 」微信公眾號更新了大量技術專題,每一篇技術文章乾貨滿滿!不少讀者已經通過閱讀「 冰河技術 」微信公眾號文章,吊打面試官,成功跳槽到大廠;也有不少讀者實現了技術上的飛躍,成為公司的技術骨幹!如果你也想像他們一樣提升自己的能力,實現技術能力的飛躍,進大廠,升職加薪,那就關注「 冰河技術 」微信公眾號吧,每天更新超硬核技術乾貨,讓你對如何提升技術能力不再迷茫!

相關文章