SpringBoot源碼解讀與原理分析(二)組件裝配

SpringBoot源碼解讀與原理分析(合集)

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源碼解讀與原理分析(合集)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容