1. 程式人生 > 其它 >07-SpringBoot自動配置深入

07-SpringBoot自動配置深入

springboot自動配置原理深入

要點:springboot的核心配置類中的註解@SpringBootApplication,這是一個核心註解。該註解主要原始碼如下

...
import ...
    
@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 {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};
.......
}
可以看出,@SpringBootApplication註解是:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
	excludeFilters = {
    	@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}),
    	@Filter(type = FilterType.CUSTOM,classes ={AutoConfigurationExcludeFilter.class})
    }
)
這三個註解的組合註解,這些註解具體作用如下所示
  • @SpringBootConfiguration註解,其原始碼部分如下

    package org.springframework.boot;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.annotation.AliasFor;
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    public @interface SpringBootConfiguration {
        @AliasFor(
            annotation = Configuration.class
        )
        boolean proxyBeanMethods() default true;
    }
    /**
    	可以看到該註解的原始碼中,除了三個註解標配以外,就是一個@Configuration,就是說該註解的功能和			@Configuration一樣,作用是宣告這個類是一個配置類,只不過@SpringBootConfiguration宣告的是一個核心		配置類,所以為什麼我們稱springboot應用的主程式類是一個核心配置類
    */
    
  • @ComponentScan註解,@SpringBootApplication註解中的該註解的屬性中添加了兩個掃描器

    @ComponentScan(
    	excludeFilters = {
        	@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}),
        	@Filter(type = FilterType.CUSTOM,classes ={AutoConfigurationExcludeFilter.class})
        }
    )
    //該註解就是指定掃描那些包,將包中的類自動spring框架自動建立物件,作為元件存放到容器中
    //上面的註解中的屬性定義了兩個掃描器,具體是什麼可以回顧spring的註解,因為這個註解是spring框架的
    
  • @EnableAutoConfiguration註解,這個是我們需要關心的核心註解。找該註解的部分原始碼

    package org.springframework.boot.autoconfigure;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import org.springframework.context.annotation.Import;
    
    @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})
    	這兩個註解的合成
    */
    
    
    1. @AutoConfigurationPackage:翻譯過來:自動配置包。部分原始碼如下

      package org.springframework.boot.autoconfigure;
      
      import java.lang.annotation.Documented;
      import java.lang.annotation.ElementType;
      import java.lang.annotation.Inherited;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      import org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar;
      import org.springframework.context.annotation.Import;
      
      @Target({ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Inherited
      @Import({Registrar.class})
      public @interface AutoConfigurationPackage {
          String[] basePackages() default {};
      
          Class<?>[] basePackageClasses() default {};
      }
      /*
      可以看到該註解中只有一個特別註解:
      	@Import({Registrar.class})
      	@Import註解表示給容器中匯入一個元件,這裡匯入的是一個Registrar.class
      	這裡可能是@Import的更高階用法,可以自己取看一下,這裡不探究
      */
      
      • 我們檢視一下Registrar類,
        static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
                Registrar() {
                }
        
                public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
                    AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
                }
        
                public Set<Object> determineImports(AnnotationMetadata metadata) {
                    return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
                }
            }
        /*
        	可以看到Registrar類是AutoConfigurationPackages類的一個靜態內部類:
        	上面原始碼中有兩個方法,主要作用是利用這個類給容器批量註冊元件,具體怎麼
        	實現,我們可以給第一個方法中的打上斷點一探究竟如下:
        */
        

        斷點示意圖

        然後DEBUG啟動類除錯,可以看到執行的方法有兩個引數:
        registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry)
            
        其中AnnotationMetadata metadata//翻譯過來就是註解的源資訊
         /*
         	就是@AutoConfigurationPackage註解標註在哪個類,
         	而這裡是@SpringBootApplication註解標註在哪裡就是該註解標註在哪裡
         	這裡是標註在主程式類上,如下圖
         */
        
      方法中有這樣一段語句:
      (String[])
          (new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])
      /*
      PackageImports(metadata)).getPackageNames()
      PackageImports表示要匯入的包裡的東西
      表示將註解的源資訊metadata拿進來,獲取到註解的包名
      這裡就是獲取主程式類所在的包名以及這個包的各個子包的包名
      我們可以看一下包名,複製
      new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames()
      右鍵->選擇Evaluate Expression->貼上進去,evaluate,可以看到是com.studymyself
      是主程式類所在包名
      
      .toArray(new String[0]這句語句就是將拿到的各個包名儲存到字串陣列中
       */
      
      這就解釋了為什麼規定我們在預設情況下要將需要作為元件的各類建立在和主程式類所在的包以及該包的子包下
    2. 註解@Import({AutoConfigurationImportSelector.class}):@AutoConfigurationPackage註解的@Import({Registrar.class})註解規定了要批量匯入元件的哪些包,而這個註解就規定要批量匯入的哪些元件,檢視AutoConfigurationImportSelector類,原始碼部分如下:

      public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
          private static final AutoConfigurationImportSelector.AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationImportSelector.AutoConfigurationEntry();
          private static final String[] NO_IMPORTS = new String[0];
          private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
          private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
          private ConfigurableListableBeanFactory beanFactory;
          private Environment environment;
          private ClassLoader beanClassLoader;
          private ResourceLoader resourceLoader;
          private AutoConfigurationImportSelector.ConfigurationClassFilter configurationClassFilter;
      
          public AutoConfigurationImportSelector() {
          }
      
          //核心方法就是這個
          public String[] selectImports(AnnotationMetadata annotationMetadata) {
              if (!this.isEnabled(annotationMetadata)) {
                  return NO_IMPORTS;
              } else {
                  AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
                  return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
              }
          }
      ......
          
       /*
       	利用getAutoConfigurationEntry(annotationMetadata);給容器批量匯入一些元件
       	selectImports方法通過獲取AnnotationMetadata annotationMetadata註解引數執行
       	方法getAutoConfigurationEntry(annotationMetadata)
       	表示自動建立獲取所掃描包中的所有元件物件,然後執行
       	 return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
       	 將獲取的元件轉換到陣列中進行返回
       */
      
      • 新增斷點進行debug除錯,如圖

        當程式拋到斷點處時,一步一步往下執行,直到執行下面第二條語句時:
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        configurations = this.removeDuplicates(configurations);
        可以檢視除錯頁面,看到configurations這個集合中有130個元件要裝配到容器中
        怎麼獲取的,是從getCandidateConfigurations方法中獲取的,我們再重新debug一遍,當執行到該方法時,進入該方法中執行
        
   ```yaml 
   #(candidate:(競選或求職的)候選人,申請人; 投考者; 應試者; 參加考試的人; 被認定適合者; 被認定有某種結局者;)(attribute v.	把…歸因於; 認為…是由於; 認為是…所為(或說、寫、作);n.屬性; 性質; 特徵;)
   當進入到this.getCandidateConfigurations(annotationMetadata, attributes)方法中執行後(獲取候選的配置),可以看到是通過獲取spring的工廠類載入器(getSpringFactoriesLoaderFactoryClass())來完成的需要裝配的元件類資訊的獲取,核心語句:
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
   進入loadFactoryNames方法中檢視,如下
   ```
   就是說
   private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader)
   方法幫我們獲取了所有應該裝配的元件資訊,是預設掃描整個專案的"META-INF/spring.factories"中獲取這個檔案,載入其中的資訊。這主要的依賴載入進來的jar包中的"META-INF/spring.factories",有些jar包的META-INF目錄下是沒有spring.factories。
   	但是最核心的是spring-boot-autoconfigure-2.2.5.RELEASE.jar這個jar包中是有spring.factories
   我們點開其中的spring.factories,可以看到# Auto Configure註解下面的資訊就是我們之前除錯時,configurations集合中儲存的,是囊括了所有場景的配置類,都是***AutoConfiguration結尾的。
       就是說是已經再配置檔案中寫死了的springboot一啟動就載入的所有場景的配置類作為元件,然後這些配置類中又為各自場景所需要的元件進行元件裝配配置。
       雖然130個場景的自動配置類在啟動的時候預設載入,但是由於這些場景的所需包以及自動配置類中的的條件裝配註解@Conditional限制了是否向容器中裝配這些元件,所以最終會按需進行配置的。所以我們在容器中看不到不需要的類的元件
  • 總結

    ```java
    1、利用getAutoConfigurationEntry(annotationMetadata);給容器中批量匯入一些元件
    2、呼叫List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)獲取到所有需要匯入到容器中的配置類
    3、利用工廠載入 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的元件
    4、從META-INF/spring.factories位置來載入一個檔案。
        預設掃描我們當前系統裡面所有META-INF/spring.factories位置的檔案
        spring-boot-autoconfigure-2.3.4.RELEASE.jar包裡面也有META-INF/spring.factories
    
       
         ```java
                 @Bean
                 @ConditionalOnBean(MultipartResolver.class)  //容器中有這個型別元件
                 @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中沒有這個名字 multipartResolver 的元件
                 public MultipartResolver multipartResolver(MultipartResolver resolver) {
                     //給@Bean標註的方法傳入了物件引數,這個引數的值就會從容器中找。
                     //SpringMVC multipartResolver。防止有些使用者配置的檔案上傳解析器不符合規範
                     // Detect if the user has created a MultipartResolver but named it incorrectly
                     return resolver;
                 }
         給容器中加入了檔案上傳解析器;
    

    SpringBoot預設會在底層配好所有的元件。但是如果使用者自己配置了以使用者的優先

     ```java
     @Bean
         @ConditionalOnMissingBean
         public CharacterEncodingFilter characterEncodingFilter() {
         }
    
       
    ##  
       
    總結:
       
         - SpringBoot先載入所有的自動配置類  xxxxxAutoConfiguration
         - 每個自動配置類按照條件進行生效,預設都會繫結配置檔案指定的值。xxxxProperties裡面拿。xxxProperties和配置檔案進行了繫結
         - 生效的配置類就會給容器中裝配很多元件
         - 只要容器中有這些元件,相當於這些功能就有了
    - 定製化配置
       
         - - 使用者直接自己@Bean替換底層的元件
      - 使用者去看這個元件是獲取的配置檔案什麼值就去修改。
       
    **xxxxxAutoConfiguration ---> 元件  --->** **xxxxProperties裡面拿值  ----> application.properties**