2.1 組件裝配
2.1.1 組件
組件:IOC容器中的核心API對(duì)象
組件裝配:將核心API配置到XML配置文件或注解配置類的行為
Spring Framework 只有一種組件裝配方式,即手動(dòng)裝配;而 Spring Boot 基于原生的手動(dòng)裝配,通過(guò)模塊裝配+條件裝配+SPI機(jī)制,完美實(shí)現(xiàn)組件的自動(dòng)裝配。
2.1.2 手動(dòng)裝配
手動(dòng)裝配,是指開(kāi)發(fā)者在項(xiàng)目中通過(guò)編寫(xiě)XML配置文件、注解配置類、配合特定注解等方式,將所需的組件注冊(cè)到IOC容器(即ApplicationContext)中。
三種手動(dòng)裝配方式(共性:需要手動(dòng)編寫(xiě)配置信息):
<!-- 基于XML配置文件的手動(dòng)配置 -->
<bean id="person" class="com.xiaowd.springboot.component.Person"/>
// 基于注解配置類的手動(dòng)裝配
@Configuration
public class ExampleConfiguration {
@Bean
public Person person() {
return new Person();
}
}
// 基于組件掃描的手動(dòng)裝配
@Component
public class DemoService {
}
@Configuration
@ComponentScan("com.xiaowd.springboot")
public class ExampleConfiguration {
}
2.1.3 自動(dòng)裝配
自動(dòng)裝配是 Spring Boot 的核心特性之一。
自動(dòng)裝配:本應(yīng)該由開(kāi)發(fā)者編寫(xiě)的配置,轉(zhuǎn)為框架自動(dòng)根據(jù)項(xiàng)目中整合的場(chǎng)景依賴,合理地做出判斷并裝配合適的Bean到IOC容器中。相比較于手動(dòng)裝配,自動(dòng)裝配關(guān)注的重點(diǎn)是整合的場(chǎng)景,而不是每個(gè)具體的場(chǎng)景中所需的組件。
- 實(shí)現(xiàn)機(jī)制:模塊裝配+條件裝配+SPI機(jī)制
- 非侵入性:默認(rèn)注冊(cè)的組件可以被覆蓋。如整個(gè)spring-jdbc時(shí),如果項(xiàng)目中已經(jīng)注冊(cè)了JdbcTemplate,則SpringBoot提供的默認(rèn)的JdbcTemplate就不會(huì)再創(chuàng)建。
- 配置禁用:在@SpringBootApplication或者@EnableAutoConfiguration注解上標(biāo)注exclude/excludeName屬性,可以禁用默認(rèn)的自動(dòng)配置類;或者在全局配置文件中聲明spring.autoconfigure.exclude屬性。
2.2 Spring Framework的模塊裝配
模塊裝配是自動(dòng)裝配的核心,可以把一個(gè)模塊所需的核心功能組件都裝配到IOC容器中。
通過(guò)標(biāo)注@EnableXXX注解,實(shí)現(xiàn)快速激活和裝配對(duì)應(yīng)的模塊
2.2.1 模塊
- 獨(dú)立的:一個(gè)個(gè)可以分解、組合、更換的獨(dú)立單元
- 功能高內(nèi)聚:一個(gè)模塊通常用于解決一個(gè)獨(dú)立的問(wèn)題
- 可相互依賴:模塊間
- 目標(biāo)明確
2.2.2 模塊裝配舉例
模塊裝配的核心原則:自定義注解+@Import導(dǎo)入組件
1.模塊裝配場(chǎng)景
使用代碼模擬構(gòu)建一個(gè)酒館,酒館里有吧臺(tái)、調(diào)酒師、服務(wù)員和老板4種不同的實(shí)體元素;酒館可以看成IOC容器,4種不同的實(shí)體元素可以看成4個(gè)組件。
目的:通過(guò)一個(gè)注解,把以上元素全部填充到酒館中。
2.聲明自定義注解@EnableTavern
@Documented
@Retention(RetentionPolicy.RUNTIME) //該注解在運(yùn)行時(shí)起效
@Target(ElementType.TYPE) // 該注解只能標(biāo)注到類上
public @interface EnableTavern {
}
3.聲明老板類Boss
public class Boss {
}
4.在@EnableTavern增加@Import注解
@Import注解源碼如下:

由源碼可知,@Import注解可以導(dǎo)入配置類、ImportSelector的實(shí)現(xiàn)類、ImportBeanDefinitionRegistrar的實(shí)現(xiàn)類,以及普通類。
接下來(lái)在@EnableTavern的@Import注解中填入Boss類,這就意味著如果一個(gè)配置類上標(biāo)注了@EnableTavern注解,就會(huì)觸發(fā)@Import的效果,向容器中導(dǎo)入一個(gè)Boss類的Bean。
@Documented
@Retention(RetentionPolicy.RUNTIME) //該注解在運(yùn)行時(shí)起效
@Target(ElementType.TYPE) // 該注解只能標(biāo)注到類上
@Import(Boss.class)
public @interface EnableTavern {
}
5.創(chuàng)建配置類
@Configuration
@EnableTavern
public class TavernConfiguration {
}
6.編寫(xiě)啟動(dòng)類測(cè)試
public class TavernApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TavernConfiguration.class);
Boss boss = ctx.getBean(Boss.class);
System.out.println(boss);
}
}

運(yùn)行結(jié)果顯示,使用getBean可以正常獲取Boss對(duì)象,說(shuō)明Boss類已經(jīng)被注冊(cè)到了IOC容器,并創(chuàng)建了一個(gè)對(duì)象。
2.2.3 導(dǎo)入配置類
1.聲明調(diào)酒師類
public class Bartender {
private String name;
public Bartender(String name) {
this.name = name;
}
// getter and setter
}
2.聲明注解配置類
@Configuration
public class BartenderConfiguration {
@Bean
public Bartender zhangsan() {
return new Bartender("張三");
}
@Bean
public Bartender lisi() {
return new Bartender("李四");
}
}
3.在@EnableTavern注解中添加BartenderConfiguration配置類
@Documented
@Retention(RetentionPolicy.RUNTIME) //該注解在運(yùn)行時(shí)起效
@Target(ElementType.TYPE) // 該注解只能標(biāo)注到類上
@Import({Boss.class, BartenderConfiguration.class})
public @interface EnableTavern {
}
4.測(cè)試運(yùn)行
public class TavernApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TavernConfiguration.class);
Map<String, Bartender> bartenders = ctx.getBeansOfType(Bartender.class);
bartenders.forEach((name, bartender) -> System.out.println(name, bartender));
}
}

運(yùn)行結(jié)果顯示,兩個(gè)調(diào)酒師對(duì)象已經(jīng)注冊(cè)到了IOC容器。
注意:
配置類@Configuration還可以被組件掃描(ComponentScan)識(shí)別到,如果配置了組件掃描,不使用@Import導(dǎo)入配置類也可以在IOC容器中找到相應(yīng)的組件。另外,本例中BartenderConfiguration本身也被注冊(cè)到了IOC容器中成為一個(gè)Bean。
2.2.4 導(dǎo)入ImportSelector實(shí)現(xiàn)類
1.ImportSelector源碼
Interface to be implemented by types that determine which @Configuration class(es) should be imported based on a given selection criteria, usually one or more annotation attributes.
ImportSelector是一個(gè)接口,它的實(shí)現(xiàn)類可以根據(jù)指定的篩選標(biāo)準(zhǔn)(通常是一個(gè)或多個(gè)注解)來(lái)決定那些配置類被導(dǎo)入。
被ImportSelector導(dǎo)入的類,最終會(huì)在IOC容器中以單實(shí)例Bean的形式創(chuàng)建并保存。
2.聲明吧臺(tái)類
public class Bar {
}
3.聲明配置類
@Configuration
public class BarConfiguration {
@Bean
public Bar bar() {
return new Bar();
}
}
4.編寫(xiě)ImportSelector的實(shí)現(xiàn)類
public class BarImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {Bar.class.getName(), BarConfiguration.class.getName()};
}
}
selectImports方法源碼:
Select and return the names of which class(es) should be imported based on the AnnotationMetadata of the importing @Configuration class.
Returns: the class names, or an empty array if none
根據(jù)導(dǎo)入的@Configuration類的注解元數(shù)據(jù)AnnotationMetadata選擇并返回要導(dǎo)入的類的類名。
注意:返回的一組類名一定是全限定類名(可直接定位)
5.在@EnableTavern注解中添加BarImportSelector
@Documented
@Retention(RetentionPolicy.RUNTIME) //該注解在運(yùn)行時(shí)起效
@Target(ElementType.TYPE) // 該注解只能標(biāo)注到類上
@Import({Boss.class, BartenderConfiguration.class, BarImportSelector.class})
public @interface EnableTavern {
}
6.測(cè)試運(yùn)行
public class TavernApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TavernConfiguration.class);
Map<String, Bar> bars = ctx.getBeansOfType(Bar.class);
bars.forEach((name, bar) -> System.out.println(name));
System.out.println("=======");
Map<String, BarConfiguration> barConfigurations = ctx.getBeansOfType(BarConfiguration.class);
barConfigurations.forEach((name, barConfiguration) -> System.out.println(name));
System.out.println("=======");
Map<String, BarImportSelector> barImportSelectors = ctx.getBeansOfType(BarImportSelector.class);
barImportSelectors.forEach((name, barImportSelector) -> System.out.println(name));
System.out.println("=======");
}
}

運(yùn)行結(jié)果顯示:
ImportSelector可以導(dǎo)入普通類(Bar),可以導(dǎo)入配置類(BarConfiguration),但沒(méi)有導(dǎo)入BarImportSelector。
7.ImportSelector的靈活性
- ImportSelector的核心是可以使開(kāi)發(fā)者采用更靈活的聲明式向IOC容器注冊(cè)Bean,其重點(diǎn)是可以靈活地注定要注冊(cè)的Bean的類。
- 如果傳入的全限定名以配置文件的形式存放在項(xiàng)目可以讀取的位置,則可以避免組件導(dǎo)入的硬編碼問(wèn)題。
- 在SpringBoot的自動(dòng)裝配中,底層就是利用了ImportSelector,實(shí)現(xiàn)從spring.factories文件中讀取自動(dòng)配置類。
2.2.5 導(dǎo)入ImportBeanDefinitionRegistrar
以編程式向IOC容器中注冊(cè)bean對(duì)象
1.聲明服務(wù)員類
public class Waiter {
}
2.編寫(xiě)ImportBeanDefinitionRegistrar的實(shí)現(xiàn)類
public class WaiterRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
registry.registerBeanDefinition("waiter222", new RootBeanDefinition(Waiter.class));
}
}
第一個(gè)參數(shù)是Bean的名稱(即ID)
第二個(gè)參數(shù)傳入的RootBeanDefinition要指定Bean的字節(jié)碼
這種方式相當(dāng)于向IOC容器注冊(cè)了一個(gè)普通的單實(shí)例bean(最終效果與組件掃描、@Bean注解的效果相同)
3.在@EnableTavern注解中添加WaiterRegistrar
@Documented
@Retention(RetentionPolicy.RUNTIME) //該注解在運(yùn)行時(shí)起效
@Target(ElementType.TYPE) // 該注解只能標(biāo)注到類上
@Import({Boss.class, BartenderConfiguration.class, BarImportSelector.class, WaiterRegistrar.class})
public @interface EnableTavern {
}
4.測(cè)試運(yùn)行
public class TavernApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TavernConfiguration.class);
Map<String, Waiter> waiters = ctx.getBeansOfType(Waiter.class);
waiters.forEach((name, waiter) -> System.out.println(name));
System.out.println("=======");
Map<String, WaiterRegistrar> waiterRegistrars = ctx.getBeansOfType(WaiterRegistrar.class);
waiterRegistrars.forEach((name, waiterRegistrar) -> System.out.println(name));
System.out.println("=======");
}
}
結(jié)果顯示:服務(wù)員對(duì)象成功注冊(cè),WaiterRegistrar不會(huì)注冊(cè)。
2.2.6 擴(kuò)展:DeferredImportSelector
ImportSelector的子接口DeferredImportSelector,類似于ImportSelector,但執(zhí)行時(shí)機(jī)比ImportSelector晚。
ImportSelector:在注解配置類的解析期間,此時(shí)配置類中的Bean方法還沒(méi)有被解析
DeferredImportSelector:在注解配置類的解析完成之后
目的:配合條件裝配(后面再深入)
1.編寫(xiě)WaiterDeferredImportSelector類
public class WaiterDeferredImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
System.out.println("DeferredImportSelector執(zhí)行了...");
return new String[] {Waiter.class.getName()};
}
}
2.ImportSelector和ImportBeanDefinitionRegistrar也加上執(zhí)行提示語(yǔ)
public class BarImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
System.out.println("ImportSelector執(zhí)行了...");
return new String[] {Bar.class.getName(), BarConfiguration.class.getName()};
}
}
public class WaiterRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
System.out.println("ImportBeanDefinitionRegistrar執(zhí)行了...");
registry.registerBeanDefinition("waiter222", new RootBeanDefinition(Waiter.class));
}
}
3.在@EnableTavern注解中添加WaiterDeferredImportSelector
@Documented
@Retention(RetentionPolicy.RUNTIME) //該注解在運(yùn)行時(shí)起效
@Target(ElementType.TYPE) // 該注解只能標(biāo)注到類上
@Import({Boss.class, BartenderConfiguration.class, BarImportSelector.class, WaiterRegistrar.class, WaiterDeferredImportSelector.class})
public @interface EnableTavern {
}
4.運(yùn)行測(cè)試

DeferredImportSelector的運(yùn)行時(shí)機(jī)比ImportSelector晚,但比ImportBeanDefinitionRegistrar早(這樣設(shè)計(jì)的原理放到后面)。
另外,DeferredImportSelector還有分組的概念(DeferredImportSelector有一個(gè)方法getImportGroup),可以對(duì)不同的DeferredImportSelector加以區(qū)分(SpringBoot使用非常少,知道即可)。
SpringBoot源碼解讀與原理分析(合集)