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

寫在前面

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

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

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotationgithub

註解說明

@Autowired註解

@Autowired 註解,能夠對類成員變量、方法和構造函數進行標註,完成自動裝配的工做。@Autowired 註解能夠放在類,接口以及方法上。在使用@Autowired以前,咱們對一個bean配置屬性時,是用以下xml文件的形式進行配置的。面試

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

@Autowired 註解的源碼以下所示。spring

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)默認優先按照類型去容器中找對應的組件,找到就賦值;bash

(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均是由冰河原創並整理的超硬核教程,面試必備!!

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

寫在最後

若是你以爲冰河寫的還不錯,請微信搜索並關注「 冰河技術 」微信公衆號,跟冰河學習高併發、分佈式、微服務、大數據、互聯網和雲原生技術,「 冰河技術 」微信公衆號更新了大量技術專題,每一篇技術文章乾貨滿滿!很多讀者已經經過閱讀「 冰河技術 」微信公衆號文章,吊打面試官,成功跳槽到大廠;也有很多讀者實現了技術上的飛躍,成爲公司的技術骨幹!若是你也想像他們同樣提高本身的能力,實現技術能力的飛躍,進大廠,升職加薪,那就關注「 冰河技術 」微信公衆號吧,天天更新超硬核技術乾貨,讓你對如何提高技術能力再也不迷茫!

相關文章
相關標籤/搜索