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


寫在前面

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

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

寫在最后

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM