寫在前面
前面幾篇中我們說(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)容
- 通過(guò)簡(jiǎn)單案例入門基于注解方式的容器配置使用
-
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
指定注入了RepositoryB
和RepositoryA
钱慢,驗(yàn)證成功。
總結(jié)
本文介紹了基于注解的Spring容器配置卿堂,簡(jiǎn)化了xml配置文件的編寫束莫,提供了2個(gè)快速入門案例。重點(diǎn)分析了@Autowired
配合各種注解靈活注入依賴覆蓋場(chǎng)景御吞。下一篇介紹在基礎(chǔ)上介紹更多的注解和類路徑的掃描麦箍。
本文由博客群發(fā)一文多發(fā)等運(yùn)營(yíng)工具平臺(tái) OpenWrite 發(fā)布