源碼學習系列之SpringBoot自動配置(篇一)

  • 2019 年 11 月 2 日
  • 筆記

源碼學習系列之SpringBoot自動配置源碼學習(篇一)

ok,本部落格嘗試跟一下Springboot的自動配置源碼,做一下筆記記錄,自動配置是Springboot的一個很關鍵的特性,也容易被忽略的屬性,因為這個屬性被包括在@SpringBootApplication註解里,所以不去跟一下源碼都不知道還有這個屬性,ps:本部落格源碼基於SpringBoot1.5.7版本

@SpringBootApplication

ok,跟一下@SpringBootApplication,發現@SpringBootApplication其實是一個複合的註解,由很多註解構成,@EnableAutoConfiguration其實只是其一部分,@EnableAutoConfiguration就是開啟自動配置的註解

//  // Source code recreated from a .class file by IntelliJ IDEA  // (powered by Fernflower decompiler)  //    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.SpringBootConfiguration;  import org.springframework.boot.context.TypeExcludeFilter;  import org.springframework.context.annotation.ComponentScan;  import org.springframework.context.annotation.FilterType;  import org.springframework.context.annotation.ComponentScan.Filter;  import org.springframework.core.annotation.AliasFor;    @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 {      ....      Class<?>[] scanBasePackageClasses() default {};  }  

@EnableAutoConfiguration

點進@EnableAutoConfiguration,比較重要的列出來:

  • @AutoConfigurationPackage
  • @Import({EnableAutoConfigurationImportSelector.class})
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({EnableAutoConfigurationImportSelector.class})  public @interface EnableAutoConfiguration {      String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";        Class<?>[] exclude() default {};        String[] excludeName() default {};  }

@AutoConfigurationPackage

先看@AutoConfigurationPackage源碼,這個註解是開啟自動配置包的,關注點在@Import({Registrar.class}),核心在Registrar類

備註:@import註解是Spring的底層註解,作用是導入一個組件到容器里

  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 {  }

Registrar 類是AutoConfigurationPackages類的靜態內部類,

 static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {          Registrar() {          }            public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {              AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());          }            public Set<Object> determineImports(AnnotationMetadata metadata) {              return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));          }      }

看一下(new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()獲取的是什麼,用idea的工具,計算表達式,可以看到其實獲取的是SpringBoot啟動類上面的包名
在這裡插入圖片描述

在這裡插入圖片描述
AnnotationMetadata:SpringBoot註解元數據

所以,@AutoConfigurationPackage開啟後,就可以將主配置類(@SpringBootApplication)所在包及其子包裡面的所有組件都掃描到Spring容器里

public static void register(BeanDefinitionRegistry registry, String... packageNames) {          if (registry.containsBeanDefinition(BEAN)) {              BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);              ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();              constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));          } else {// Spring容器里沒有找到對應組件              /* 將組件註冊到Spring容器里 */              GenericBeanDefinition beanDefinition = new GenericBeanDefinition();                  beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class);  beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);              beanDefinition.setRole(2);              registry.registerBeanDefinition(BEAN, beanDefinition);          }        }

ok,跟了源碼,當然只是簡單跟一下,沒有特別細的跟,這個過程可以看出這裡的組件掃描只是掃描主配置類(@SpringBootApplication)所在包及其子包裡面的所有組件,所以,寫了例子驗證一下:
在Application類包外寫個Controller測試類
在這裡插入圖片描述

@RestController  public class TestController {        @GetMapping("/hello")      public String hello(){          return "hello world!";      }  }

啟動項目,進行訪問,發現都是404找不到這個介面,再將這個Controller放在包裡面,才可以掃描到

在這裡插入圖片描述

@Import({EnableAutoConfigurationImportSelector.class})

package org.springframework.boot.autoconfigure;    import org.springframework.core.type.AnnotationMetadata;    /** @deprecated */  @Deprecated  public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector {      public EnableAutoConfigurationImportSelector() {      }        protected boolean isEnabled(AnnotationMetadata metadata) {          return this.getClass().equals(EnableAutoConfigurationImportSelector.class) ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;      }  }  

主要看一下其基類AutoConfigurationImportSelector程式碼,看一下selectImport方法:

/**裝載很多自動配置類,以全類名的方式返回一個字元數組**/  public String[] selectImports(AnnotationMetadata annotationMetadata) {          if (!this.isEnabled(annotationMetadata)) {              return NO_IMPORTS;          } else {              try {                  //獲取自動配置的元數據                  AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);                  //裝載配置屬性                  AnnotationAttributes attributes = this.getAttributes(annotationMetadata);                  //通過類載入器讀取所有的配置類全類名                  List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);                  configurations = this.removeDuplicates(configurations);                  configurations = this.sort(configurations, autoConfigurationMetadata);                  Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);                  this.checkExcludedClasses(configurations, exclusions);                  configurations.removeAll(exclusions);                  configurations = this.filter(configurations, autoConfigurationMetadata);                  this.fireAutoConfigurationImportEvents(configurations, exclusions);                  return (String[])configurations.toArray(new String[configurations.size()]);              } catch (IOException var6) {                  throw new IllegalStateException(var6);              }          }      }
  • AutoConfigurationMetadata資訊,獲取整個JavaEE體系的一些配置類,當然是Springboot集成的,比如有WebMvcAutoConfiguration自動配置類
    在這裡插入圖片描述

跟一下getCandidateConfigurations方法,SpringFactoriesLoader是Spring-code工程的工廠載入類,使用SpringFactoriesLoader,需要在模組的META-INF/spring.factories文件自己配置屬性,這個Properties格式的文件中的key是介面、註解、或抽象類的全名,value是以逗號 「 , 「 分隔的實現類,使用SpringFactoriesLoader可以實現將相應的實現類注入Spirng容器

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {          List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());          Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");          return configurations;      }

loadFactoryNames方法程式碼,

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";      /* 將spring.factories的類都裝載到Spring容器*/       public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {          String factoryClassName = factoryClass.getName();            try {          //將META-INF/spring.factories文件里配置的屬性都裝載到Enumeration數據結構里              Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");              ArrayList result = new ArrayList();              //遍歷獲取屬性,然後再獲取對應的配置類全類名              while(urls.hasMoreElements()) {                  URL url = (URL)urls.nextElement();                  Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));                  String factoryClassNames = properties.getProperty(factoryClassName);                  result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));              }                return result;          } catch (IOException var8) {              throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);          }      }

然後META-INF/spring.factories文件放在哪?很顯然是放在Springboot的自動配置模組里,如圖:
在這裡插入圖片描述

所以,@Import({EnableAutoConfigurationImportSelector.class})開啟之後,主要是EnableAutoConfigurationImportSelector這個類的作用就是在SpringBoot啟動時候將從SpringBoot自動配置工程的META-INF/spring.factories文件中獲取指定的值,經過SpringFactoriesLoader載入之後將很多自動配置類載入到Spring容器,所以我們不需要配置,mvc等等默認配置就已經隨著SpringBoot啟動而自動生效

ok,Springboot的自動配置類都在這個包里,源碼很多,所以本部落格只是簡單跟一下源碼
在這裡插入圖片描述