spring boot自動裝配機制 學習筆記

spring boot自動裝配機制 學習筆記

閒談

爲什麼陳咬金靠着三板斧就可以成爲國公?
那學習spring自動裝配的三板斧就必須得瞭解了,學好spring是學好spring boot的基礎,因爲相比spring,spring boot沒有新的技術點,它是服務於spring框架的框架,用於快速構建springweb項目,遵守約定大於配置的原則,
前邊我們通過spring的SPI機制(滿足目錄一致,文件名一致,key要存在並符合當前的加載)自定義了一個自己的starter組件,並在springboot項目中成功引用,通過測試,但是當時我們只是用到了最簡單的制動裝配,現在我們來羅列一下springboot中的自動裝配機制

spring以及spring boot相關概念理解

IOC spring mvc DI DL AOP
最早spring 的bean的配置,在applicationContext.xml 中,配置bean 標籤,
spring 3.0開始,由於Java5 開始支持Javaconfig ,所有spring開始支持註解的方式代替之前的大量的濫用的xml配置文件,有了spring 3.0,纔有了springboot 出現的可能

約定大於配置的體現

1.maven 或gradle的目錄結構,默認會以jar的方式打包,默認會有resource文件夾
2.spring-boot-starter-web:內置Tomcat、resource、templete,static
3.默認的application.properties 或yml
4.默認通過 spring.profiles.active 屬性來決定運行環境時讀取的配置文件
5.EnableAutoConfiguration 默認對於依賴的 starter 進行自動裝載

註解的理解

1 @AutoConfiguration
2 starter
3. actuator
4. SpringBoot CLI

IOC控制反轉
@SpringBootApplication 複合註解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {

SpringBootApplication 本質上是由 3 個註解組成,分別是

  1. @Configuration //配置類註解
  2. @EnableAutoConfiguration //自動裝配註解
  3. @ComponentScan//掃描註解

簡單分析@Configuration

1.定義普通Javabean
//希望這個類被springj加載 xml的形式 <bean  class="com.DemoClass"
public class DemoClass {
    public void say(){
        System.out.println("say hello");
    }
}
2.定義配置類,加載普通Javabean

相當於一個applicationContext.xml(在配置的時代,我們通常會將不同的spring xml 導入到applicationContext.xml中,達到業務bean配置的分離)加載bean,同理@Configuration也可以是多個
@Configuration

public class DemoConfiguration {
    //單利,每次通過容器獲取,返回同一個
    @Bean  //等同於 <bean  class="com.DemoClass" 同樣的 @Scope 上也有很多屬性
    public DemoClass getDemo(){
        return new DemoClass();
    }
}
3.模擬springboot 啓動 run()測試加載過程
public class DemoMainTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext=new AnnotationConfigApplicationContext(DemoConfiguration.class);
        DemoClass demoClass= (DemoClass) annotationConfigApplicationContext.getBean(DemoClass.class);
        System.out.println(demoClass);
    }
}
4.測試結果
20:58:26.312 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getDemo'
com.example.demo.demoone.DemoClass@77f80c04

簡單分析@ComponentScan

等同於<context:component-scan
掃描什麼?
掃描 @Component 註解以及其派生出來的註解
如 @Service @Controller@Repository @RestController 爲什麼會掃描這些註解呢,因爲他們都是Component派生出來的,屬於Component系列
託管這些註解標註的類

1.創建帶@Component 註解的Javabean
@Component
public class DemoClass {
    public void say(){
        System.out.println("say hello");
    }
}
2.利用@ComponentScan 掃描當前類所在的包,或者掃描指定的包下,加載bean
@ComponentScan  //默認掃描當前路徑下的包
public class DemoMainTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext=new AnnotationConfigApplicationContext(DemoMainTest.class);
        DemoClass demoClass= (DemoClass) annotationConfigApplicationContext.getBean(DemoClass.class);
        System.out.println(demoClass);
    }
}
3測試結果
21:31:17.956 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'demoClass'
com.example.demo.demotow.DemoClass@4f6ee6e4

EnableAutoConfiguration 非常重要,壓軸

EnableAutoConfiguration 的 主 要 作 用 其 實 就 是 幫 助springboot 應用把所有符合條件的@Configuration 配置都加載到當前 SpringBoot 創建並使用的 IoC 容器中。
爲表重要,我們貼點代碼,前兩個都不用貼

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

@AutoConfigurationPackage@Import({AutoConfigurationImportSelector.class})
有興趣也瞭解一下@EnableWebMvc 引入了 MVC 框架在 Spring 應用中需要用到的所有
bean; @EnableScheduling 開啓計劃任務的支持(定時任務)

1. @Import理解

等價於 xml配置的 <import resource .../>
import 就是將多個分開的配置合併在一個配置中。與在JavaConfig 中該註解所表達的意義是一樣的。
@Import可以配置三種不同的 class

1. 第一種基於普通 bean 或者帶有
     @Configuration 的 bean 進行諸如
2. 實現 ImportSelector 接口進行動態注入     將方法返回的字符串數組作爲bean注入到容器中
3. 實現 ImportBeanDefinitionRegistrar 接口進行動態注入   自定義註冊bean
2. ImportSelector理解

上面的代碼@Import({AutoConfigurationImportSelector.class}),是因爲AutoConfigurationImportSelector實現了importSelector接口,其實現藉助了Spring 框架提供的一個工具類 SpringFactoriesLoader 的支持,以及用到了 Spring 提供的條件註解@ConditionalOnXXXXX(*.class) 等同於spring-autoconfiguration-metadata.properties 中的條件 XXX.ConditionalOnMissingClass=,和過濾‘exclude’ 選擇性的針對需要加載的 bean 進行條件過濾

SpringFactoriesLoader理解

加載器,它其實和java 中的 SPI 機制的原理是一樣,不過它比 SPI 更好的點,在於不會一次性加載所有的類,而是根據 key 進行加載。首先, SpringFactoriesLoader 的作用是從classpath/META-INF/spring.factories 文件中,根據 key 來加載對應的類到 spring IoC 容器中。
AutoConfigurationImportSelector藉助它完成類的加載,它的key 是 org.springframework.boot.autoconfigure.EnableAutoConfiguration ,對應N個字符串,SpringFactoriesLoader 加載後,逐一處理
如有以下的values

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\

SpringFactoriesLoader 這個類我們也可以直接用,搞一些我們自己特殊東西

AutoConfigurationImportSelector 理解

會先掃描 spring-autoconfiguration-metadata.properties文件,最後在掃描 spring.factories 對應的類時,會結合前面的元數據進行過濾,爲什麼要過濾呢? 原因是很多的@Configuration 其實是依託於其他的框架來加載的,如果當前的 classpath 環境下沒有相關聯的依賴,則意味着這些類沒必要進行加載,所以,通過這種條件過濾可以有效的減少@configuration 類的數量從而降低SpringBoot 的啓動時間

Conditional 和Conditional系列的 理解

Conditional 有兩種方式,可以在 spring-autoconfiguration-metadata.properties 中定義,如com.test.BeanConfiguration.ConditionalOnMissingClass=com.alibaba.fastjson.JSON
也可以直接使用@ConditionalOnMissingClass("com.alibaba.fastjson.JSON") 這種註解,他們是等價的

Conditions 描述

@ConditionalOnBean 在存在某個 bean 的時候
@ConditionalOnMissingBean 不存在某個 bean 的時候
@ConditionalOnClass 當前 classpath 可以找到某個類型的類時
@ConditionalOnMissingClass 當前 classpath 不可以找到某個類型的類時
@ConditionalOnResource 當前 classpath 是否存在某個資源文件
@ConditionalOnProperty 當前 jvm 是否包含某個系統屬性爲某個值
@ConditionalOnWebApplication 當前 spring context 是否是 web 應用程序
3. ImportBeanDefinitionRegistrar 理解

和ImportSelector 功能類似,也支持條件注入,此方法可以對裝配的類做一個過濾處理
主要工作的方法是
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)

5@AutoConfigurationPackage

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};

Class<?>[] basePackageClasses() default {};

}
最新的版本中,這個註解,導入了Registrar.class,它是ImportBeanDefinitionRegistrar的一個子類,之前的版本中是直接導入ImportBeanDefinitionRegistrar.class

通過@Configuration裝配Bean
1創建now包,並創建簡單Java bean
public class BeanOne {
}
2.創建other 包,並創建另一個簡單Java bean
public class BeanTow {
}
3.now包創建配置類
@Configuration
public class BeanConfiguration {
    @Bean
    public BeanOne getBeanOne(){
        return new BeanOne();
    }
}
4.now包創建main類驗證加載
public class DemoMainTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext=new AnnotationConfigApplicationContext(BeanConfiguration.class);
        String [] defNames=annotationConfigApplicationContext.getBeanDefinitionNames();
        for (String  str:defNames) {
            System.out.println(str);
        }

    }
}
5.返回結果
beanConfiguration
getBeanOne

沒有加載到BeanTow 怎麼才能加載到呢,在other包中創建 配置類,有import能導入@Configuration 註解的類,進行加入

通過@Import(OtherConfiguration.class) 裝配別的路徑下@Configuration註解的Bean

6.在 5 的基礎上other包中創建 配置類
@Configuration
public class OtherConfiguration {
    @Bean
    public BeanTow getBeanTow(){
        return new BeanTow();
    }
}
7.在now包的配置類中@ import()導入 other 包中的配置類OtherConfiguration
@Configuration
@Import(OtherConfiguration.class)
public class BeanConfiguration {
    @Bean
    public BeanOne getBeanOne(){
        return new BeanOne();
    }
}
8.驗證other 包 中的類有沒有被自動裝配

返回結果,加載成功

beanConfiguration
com.example.demo.demnthree.other.OtherConfiguration
getBeanTow
getBeanOne

通過ImportBeanDefinitionRegistrar 和 ImportSelector 裝配Bean實戰

1. 簡單的三個Javabean

public class AttachService {
}
public class CacheService {
}
public class LoggerService {
}

2.自定義配置類 ImportSelector

public class CacheImportSelector implements ImportSelector {
    //AnnotationMetadata 註解中的元數據
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        //獲取@EnableDefineService註解的元信息也就是 exclude = {LoggerService.class}
        Map<String,Object> attributes= annotationMetadata.getAnnotationAttributes(EnableDefineService.class.getName());
        //可以返回一些信息,暫時寫死
        System.out.println(attributes.get("exclude").getClass().getName());
        //排除邏輯的判斷,即不加載哪個類,可以將此放入到配置文件中
        if(attributes.get("exclude")==null){
            //將CacheService託管給spring ioc
            return new String[] {CacheService.class.getName()};
        }
        else{
             //將CacheService和AttachService託管給spring ioc
            return new String[] {CacheService.class.getName(),AttachService.class.getName()}; 
           
        }
        //將擴展點放入到配置文件中,SP擴展點相當於從所有resource/MATE-INF/spring.fatories配置文件中,
        //實現這個要依賴類似於SpringFactoriesLoader 的使用
        //        return new String[] {"org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration",
       //         "org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration",
       //         "org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration"};
//總而言之,只能根據元數據,或者condition 做過條件濾處理,返回一個需要加載的 類的字符串數組,由spring來加載
    }
}

3.自定義配置類 LoggerDefinetionRegister 擴展點

public class LoggerDefinetionRegister implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
          Class beanClass =LoggerService.class;
        RootBeanDefinition beanDefinition =new RootBeanDefinition(beanClass);//封裝成BeanDefinition
        //相當於<bean name=“XX”
        String name= StringUtils.uncapitalize(beanClass.getSimpleName());//首字母小寫
         //可以自定義bean name 與CacheImportSelector 不同name,而且直接就能裝配了,而CacheImportSelector 只能  過濾,返回字符串數組,由spring裝配
        registry.registerBeanDefinition(name,beanDefinition);
    }
}

4.自定義註解中,導入CacheImportSelector.class,LoggerDefinetionRegister.class

前邊說過@import
2. 實現 ImportSelector 接口進行動態注入
3. 實現 ImportBeanDefinitionRegistrar 接口進行動態注入

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({CacheImportSelector.class,LoggerDefinetionRegister.class}) //導入的不是Configuration,而是ImportSelector和ImportBeanDefinitionRegistrar
public @interface EnableDefineService {
    //配置一些方法
    Class<?>[] exclude() default {};
}

測試註解,並添加 exclude進行條件裝配,

@SpringBootApplication
@EnableDefineService(exclude = {LoggerService.class})
public class EnableDefineMain {
    public static void main(String[] args) {
        ConfigurableApplicationContext ca= SpringApplication.run(EnableDefineMain.class,args);
        String[] names=ca.getBeanDefinitionNames();
        for (String s:names) {
            System.out.println(s);
        }
    }
}

運行結果

com.example.demo.demofour.CacheService
com.example.demo.demofour.AttachService
org.springframework.boot.autoconfigure.AutoConfigurationPackages
loggerService

將類的加載條件放到配置文件中去實戰

請查看 手把手 自定義XXX-spring-boot-starter組件章節

本次學習結束,之前我們已經自定義了starter
下一章是進行自定義多數據庫連接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章