Spring系列9:基于注解的Spring容器配置

寫在前面

前面幾篇中我們說(shuō)過(guò)匈睁,Spring容器支持3種方式進(jìn)行bean定義信息的配置卷哩,現(xiàn)在具體說(shuō)明下:

  • XML:bean的定義和依賴都在xml文件中配置蛋辈,比較繁雜。
  • Annotation-based :通過(guò)直接的方式注入依賴,xml文件配置掃描包路徑冷溶,xml簡(jiǎn)化很多渐白。
  • Java-based: 通過(guò)配置類和注解批量掃描和注冊(cè)bean,不再需要xml文件。

前面的案例都是基于XML的逞频,這篇介紹Annotation-based方式纯衍。

本文內(nèi)容

  1. 通過(guò)簡(jiǎn)單案例入門基于注解方式的容器配置使用
  2. Autowired的詳細(xì)使用,配合@Primary苗胀、@Qulifier

案例入門1

該入門案例的bean的定義信息在xml文件這個(gè)襟诸,使用注解來(lái)進(jìn)行依賴注入。

@Autowired:將構(gòu)造函數(shù)柒巫、字段励堡、設(shè)置方法或配置方法標(biāo)記為由 Spring 的依賴注入工具自動(dòng)裝配。

定義類并通過(guò)@Autowird注入依賴

public class BeanOne {
}

public class BeanTwo {
    // 注解注入 BeanOne
    @Autowired
    private BeanOne beanOne;

    @Override
    public String toString() {
        return "BeanTwo{" +
                "beanOne=" + beanOne +
                '}';
    }
}

xml文件啟用注解配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
">
    <!--注解配置啟用-->
    <context:annotation-config></context:annotation-config>
    
    <bean class="com.crab.spring.ioc.demo06.BeanOne" id="beanOne">
        <!--可以通過(guò)常規(guī)的依賴注入方式注入-->
    </bean>
    <!--依賴是通過(guò)注解自動(dòng)注入的-->
    <bean class="com.crab.spring.ioc.demo06.BeanTwo" id="beanTwo"/>

</beans>

獲取容器中的bean進(jìn)行使用

@org.junit.Test
public void test_annotation_config() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring2.xml");
        BeanTwo beanTwo = context.getBean(BeanTwo.class);
        System.out.println(beanTwo);
        context.close();
    }
    
// 輸出
BeanTwo{beanOne=com.crab.spring.ioc.demo06.BeanOne@491666ad}

案例入門2

入門案例1中簡(jiǎn)化傳統(tǒng)的xml配置依賴注入的方式堡掏,但是bean的定義信息依舊是需要手動(dòng)配置在xml中的应结。可以掃描bean的方式進(jìn)一步簡(jiǎn)化泉唁,增加配置節(jié)即可鹅龄。

<context:component-scan base-package="org.example"/>

通過(guò)注解標(biāo)記類為bean

@Component:表示帶注釋的類是“組件”。在使用基于注釋的配置和類路徑掃描時(shí)亭畜,此類被視為自動(dòng)檢測(cè)的候選對(duì)象

@Component
public class RepositoryA implements RepositoryBase {
}
@Component
public class RepositoryB  implements RepositoryBase {
}

@Component
public class ServiceA {
    @Autowired
    private RepositoryA repositoryA;

    @Autowired
    private RepositoryB repositoryB;
    
    // 省略 Getter toString()
}

xml配置掃描包路徑

xml配置文件扮休,非常簡(jiǎn)潔

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!--掃描指定包下的組件bean并自動(dòng)DI-->
    <context:annotation-config/>
    <context:component-scan base-package="com.crab.spring.ioc.demo06"/>
    <!--不再有繁雜的bean的定義-->

</beans>

獲取容器中bean使用

測(cè)試程序和上一篇的類似。

package com.crab.spring.ioc.demo06;

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/13 15:11
 * @關(guān)于我 請(qǐng)關(guān)注公眾號(hào) 螃蟹的Java筆記 獲取更多技術(shù)系列
 */
public class Test {

    @org.junit.Test
    public void test() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");
        ServiceA serviceA = context.getBean(ServiceA.class);
        RepositoryA repositoryA = context.getBean(RepositoryA.class);
        RepositoryB repositoryB = context.getBean(RepositoryB.class);
        System.out.println(serviceA);
        System.out.println(serviceA.getRepositoryA() == repositoryA);
        System.out.println(serviceA.getRepositoryB() == repositoryB);
        context.close();
    }
}

運(yùn)行結(jié)果

ServiceA{repositoryA=com.crab.spring.ioc.demo06.RepositoryA@27c86f2d, repositoryB=com.crab.spring.ioc.demo06.RepositoryB@197d671}
true
true

結(jié)論:RepositoryA拴鸵、RepositoryB玷坠、ServiceA 都掃描到容器中管理,ServiceA的依賴已經(jīng)自動(dòng)DI了劲藐。相比傳統(tǒng)的XML方式八堡,這種方式簡(jiǎn)潔省心不少。

注意:XML方式和注解配置方式可以混合用聘芜。注解注入在 XML 注入之前執(zhí)行兄渺。因此,XML 配置會(huì)覆蓋注解方式注入的汰现。

@Required使用

@Required 注解適用于 bean 屬性設(shè)置方法挂谍,如果容器這個(gè)沒(méi)有對(duì)應(yīng)的bean則會(huì)拋出異常。

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

@Required 注解和RequiredAnnotationBeanPostProcessor 從 Spring Framework 5.1 開始正式棄用瞎饲,更好的方式是使用構(gòu)造函數(shù)注入(或 InitializingBean.afterPropertiesSet() 的自定義實(shí)現(xiàn)或自定義 @PostConstruct 方法以及 bean 屬性設(shè)置器方法)口叙。

@Autowired使用

將構(gòu)造函數(shù)、字段嗅战、Setter方法或配置方法標(biāo)記為由 Spring 的依賴注入工具自動(dòng)裝配庐扫, required指定是否必須。這是 JSR-330 @Inject注解的替代方案。

標(biāo)記在構(gòu)造函數(shù)形庭、字段铅辞、Setter方法上

來(lái)一個(gè)綜合3種注解位置的類

@Component
public class Service1 {
    private RepositoryA repositoryA;
    private RepositoryB repositoryB;
    // 標(biāo)記field
    @Autowired
    private RepositoryC repositoryC;

    // 標(biāo)記構(gòu)造函數(shù)
    @Autowired
    public Service1(RepositoryA repositoryA) {
        this.repositoryA = repositoryA;
    }

    @Autowired
    public void setRepositoryB(RepositoryB repositoryB) {
        this.repositoryB = repositoryB;
    }

    @Override
    public String toString() {
        return "Service1{" +
                "repositoryA=" + repositoryA +
                ", repositoryB=" + repositoryB +
                ", repositoryC=" + repositoryC +
                '}';
    }
}

測(cè)試方法和結(jié)果如下

    @org.junit.Test
    public void test_autowired() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");
        Service1 bean = context.getBean(Service1.class);
        System.out.println(bean);
        context.close();
    }

// 結(jié)果
Service1{repositoryA=com.crab.spring.ioc.demo06.RepositoryA@79efed2d, repositoryB=com.crab.spring.ioc.demo06.RepositoryB@2928854b, repositoryC=com.crab.spring.ioc.demo06.RepositoryC@27ae2fd0}

從輸出結(jié)果來(lái)看,三種位置注入依賴都是可以的萨醒。

從 Spring Framework 4.3 開始斟珊,如果目標(biāo) bean 僅定義一個(gè)構(gòu)造函數(shù),則不再需要在此類構(gòu)造函數(shù)上使用 @Autowired 注釋富纸。

@Autowired注入集合

默認(rèn)順序

容器中所有符合類型的都會(huì)自動(dòng)注入囤踩,默認(rèn)順序是bean注冊(cè)定義的順序。

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/15 17:48
 * @關(guān)于我 請(qǐng)關(guān)注公眾號(hào) 螃蟹的Java筆記 獲取更多技術(shù)系列
 */
@Component
public class Service2 {
    @Autowired
    private List<RepositoryBase> repositoryList;
    @Autowired
    private Set<RepositoryBase> repositorySet;
    @Autowired
    private RepositoryBase[] repositoryArr;
    
    // 省略 Getter和Setter
}

測(cè)試和結(jié)果

    @org.junit.Test
    public void test_collection() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");
        Service2 service2 = context.getBean(Service2.class);
        System.out.println(service2.getRepositoryList());
        System.out.println(service2.getRepositorySet());
        Arrays.stream(service2.getRepositoryArr()).forEach(System.out::println);
        context.close();
    }
[com.crab.spring.ioc.demo06.RepositoryA@2ddc8ecb, com.crab.spring.ioc.demo06.RepositoryB@229d10bd, com.crab.spring.ioc.demo06.RepositoryC@47542153]
[com.crab.spring.ioc.demo06.RepositoryA@2ddc8ecb, com.crab.spring.ioc.demo06.RepositoryB@229d10bd, com.crab.spring.ioc.demo06.RepositoryC@47542153]
com.crab.spring.ioc.demo06.RepositoryA@2ddc8ecb
com.crab.spring.ioc.demo06.RepositoryB@229d10bd
com.crab.spring.ioc.demo06.RepositoryC@47542153

從結(jié)果看晓褪,順序是ABC堵漱。

通過(guò)@Ordered指定順序

RepositoryA RepositoryB RepositoryC上加上@Ordered,數(shù)值越小優(yōu)先級(jí)越高涣仿。

@Component
@Order(0) // 指定注入集合時(shí)的順序
public class RepositoryA implements RepositoryBase {
}

@Component
@Order(-1)
public class RepositoryB  implements RepositoryBase {
}

@Component
@Order(-2)
public class RepositoryC implements RepositoryBase{
}

還是運(yùn)行上面的測(cè)試test_collection,觀察輸出順序勤庐。

[com.crab.spring.ioc.demo06.RepositoryC@56a6d5a6, com.crab.spring.ioc.demo06.RepositoryB@309e345f, com.crab.spring.ioc.demo06.RepositoryA@7a4ccb53]
[com.crab.spring.ioc.demo06.RepositoryA@7a4ccb53, com.crab.spring.ioc.demo06.RepositoryB@309e345f, com.crab.spring.ioc.demo06.RepositoryC@56a6d5a6]
com.crab.spring.ioc.demo06.RepositoryC@56a6d5a6
com.crab.spring.ioc.demo06.RepositoryB@309e345f
com.crab.spring.ioc.demo06.RepositoryA@7a4ccb53

從結(jié)果看,順序是CBA好港,順序符合預(yù)期愉镰。

@Autowired注入Map

只要map的鍵類型是String,即使是類型化的 Map 實(shí)例也可以自動(dòng)裝配钧汹。

定義一個(gè)類注入map并打印

@Component
public class Service3 {
    @Autowired
    private Map<String, RepositoryBase> repositoryMap;

    public void printMap() {
        this.repositoryMap.entrySet().forEach(entry -> {
            System.out.println(entry.getKey() + "--" + entry.getKey());
        });
    }
}

測(cè)試一下輸出結(jié)果

    @org.junit.Test
    public void test_map() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");
        System.out.println("注入map的鍵值對(duì)如下:");
        Service3 service3 = context.getBean(Service3.class);
        service3.printMap();
        context.close();
    }

注入map的鍵值對(duì)如下:
repositoryA--repositoryA
repositoryB--repositoryB
repositoryC--repositoryC

從結(jié)果看丈探,成功注入到map中了。

@Autowired結(jié)合@Primary

@Primary指示當(dāng)多個(gè)候選者有資格自動(dòng)裝配單值依賴項(xiàng)時(shí)拔莱,應(yīng)優(yōu)先考慮 該bean碗降。

先看一個(gè)不加@Primary的案例。

@Component
@Order(0) // 指定注入集合時(shí)的順序
public class RepositoryA implements RepositoryBase {
}

@Component
@Order(-1)
public class RepositoryB  implements RepositoryBase {
}

@Component
@Order(-2)
public class RepositoryC implements RepositoryBase{
}
@Component
public class Service4 {

    @Autowired
    private RepositoryBase repositoryBase;

    @Override
    public String toString() {
        return "Service4{" +
                "repositoryBase=" + repositoryBase +
                '}';
    }
}

由于容器中有3個(gè)RepositoryBase塘秦,Spring無(wú)法決定選擇哪一個(gè)讼渊,因此會(huì)拋出UnsatisfiedDependencyException如下。

    @org.junit.Test
    public void test_require() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");
        Service4 service4 = context.getBean(Service4.class);
        System.out.println(service4);
        context.close();
    }
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'service4': Unsatisfied dependency expressed through field 'repositoryBase'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.crab.spring.ioc.demo06.RepositoryBase' available: expected single matching bean but found 3: repositoryA,repositoryB,repositoryC

RepositoryC加上@Primary

@Component
@Order(-2)
@Primary
public class RepositoryC implements RepositoryBase{
}

再運(yùn)行上面的測(cè)試嗤形,成功注入了RepositoryC精偿。

Service4{repositoryBase=com.crab.spring.ioc.demo06.RepositoryC@4df50bcc}

@Autowired配合Optional

@Autowired有個(gè)屬性required指示依賴是否是非必須即可選的弧圆,默認(rèn)是false赋兵。可以用java.util.Optional包裝下搔预。

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/14 11:54
 * @關(guān)于我 請(qǐng)關(guān)注公眾號(hào) 螃蟹的Java筆記 獲取更多技術(shù)系列
 */
@Component
public class Service5 {
    private RepositoryA repositoryA;

    public RepositoryA getRepositoryA() {
        return repositoryA;
    }
    @Autowired
    public void setRepositoryA(Optional<RepositoryA> repositoryOptional) {
        RepositoryA repositoryA = repositoryOptional.orElseGet(() -> new RepositoryA());
        this.repositoryA = this.repositoryA;
    }
}

從 Spring Framework 5.0 開始霹期,還可以使用 @Nullable 注解來(lái)表達(dá)可空的概念。

@Component
public class Service6 {
    private RepositoryA repositoryA;

    @Autowired
    public void setRepositoryA(@Nullable RepositoryA repositoryA) {
        this.repositoryA = this.repositoryA;
    }
}

配合@Qualifier

上面提到拯田,當(dāng)容器中具有多個(gè)實(shí)例需要確定一個(gè)主要候選人時(shí)历造,@Primary 是一種使用類型自動(dòng)裝配的有效方法,具有多個(gè)實(shí)例。當(dāng)需要對(duì)選擇過(guò)程進(jìn)行更多控制時(shí)吭产,也可以使用 Spring 的 @Qualifier 注解侣监。通過(guò)限定符值與特定參數(shù)相關(guān)聯(lián),縮小類型匹配的范圍臣淤,以便為每個(gè)參數(shù)選擇特定的 bean橄霉。來(lái)看案例。

快速使用

依賴類的配置

@Component
@Order(0)
public class RepositoryA implements RepositoryBase {
}

@Component("repositoryB") // 指定了名稱
@Order(-1)
public class RepositoryB  implements RepositoryBase {
}

@Component
@Order(-2)
@Primary  // 標(biāo)記為主要的候選者
public class RepositoryC implements RepositoryBase{
}

@Qualifier在字段和構(gòu)造函數(shù)上指定bean的名稱

@Component
public class Service7 {
    // 指定注入repositoryB
    @Autowired
    @Qualifier("repositoryB")
    private RepositoryBase repository;

    private RepositoryBase repository2;

    // 在構(gòu)造函數(shù)中指定注入repositoryA
    @Autowired
    public Service7(@Qualifier("repositoryA") RepositoryBase repository2) {
        this.repository2 = repository2;
    }
    // 省略
}

測(cè)試一下邑蒋,觀察輸出結(jié)果姓蜂。

    @org.junit.Test
    public void test_qualifier() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");
        Service7 service7 = context.getBean(Service7.class);
        System.out.println(service7);
        context.close();
    }
Service1{repository=com.crab.spring.ioc.demo06.RepositoryB@222114ba, repository2=com.crab.spring.ioc.demo06.RepositoryA@16e7dcfd}

結(jié)論:RepositoryC標(biāo)記了@Primary注解,正常情況下會(huì)注入該類實(shí)例医吊。通過(guò)@Qualifier指定注入了RepositoryBRepositoryA钱慢,驗(yàn)證成功。

總結(jié)

本文介紹了基于注解的Spring容器配置卿堂,簡(jiǎn)化了xml配置文件的編寫束莫,提供了2個(gè)快速入門案例。重點(diǎn)分析了@Autowired配合各種注解靈活注入依賴覆蓋場(chǎng)景御吞。下一篇介紹在基礎(chǔ)上介紹更多的注解和類路徑的掃描麦箍。

本篇源碼地址: https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo06

本文由博客群發(fā)一文多發(fā)等運(yùn)營(yíng)工具平臺(tái) OpenWrite 發(fā)布

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市陶珠,隨后出現(xiàn)的幾起案子挟裂,更是在濱河造成了極大的恐慌,老刑警劉巖揍诽,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诀蓉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡暑脆,警方通過(guò)查閱死者的電腦和手機(jī)渠啤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)添吗,“玉大人沥曹,你說(shuō)我怎么就攤上這事〉” “怎么了妓美?”我有些...
    開封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)鲤孵。 經(jīng)常有香客問(wèn)我壶栋,道長(zhǎng),這世上最難降的妖魔是什么普监? 我笑而不...
    開封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任贵试,我火速辦了婚禮琉兜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘毙玻。我一直安慰自己豌蟋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開白布桑滩。 她就那樣靜靜地躺著夺饲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪施符。 梳的紋絲不亂的頭發(fā)上往声,一...
    開封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音戳吝,去河邊找鬼浩销。 笑死,一個(gè)胖子當(dāng)著我的面吹牛听哭,可吹牛的內(nèi)容都是我干的慢洋。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼陆盘,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼普筹!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起隘马,我...
    開封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤枉昏,失蹤者是張志新(化名)和其女友劉穎督弓,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡啃匿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年柱嫌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谒养。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片游岳。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖邀泉,靈堂內(nèi)的尸體忽然破棺而出嬉挡,到底是詐尸還是另有隱情,我是刑警寧澤汇恤,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布庞钢,位于F島的核電站,受9級(jí)特大地震影響屁置,放射性物質(zhì)發(fā)生泄漏焊夸。R本人自食惡果不足惜仁连,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一蓝角、第九天 我趴在偏房一處隱蔽的房頂上張望阱穗。 院中可真熱鬧,春花似錦使鹅、人聲如沸揪阶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鲁僚。三九已至,卻和暖如春裁厅,著一層夾襖步出監(jiān)牢的瞬間冰沙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工执虹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拓挥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓袋励,卻偏偏與公主長(zhǎng)得像侥啤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子茬故,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

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