springboot-自動配置

1. 概述

本文,咱們來分享 Spring Boot 自動配置的實現源碼。在故事的開始,咱們先來講兩個事情:html

  • 自動配置和自動裝配的區別?
  • Spring Boot 配置的原理

2. 自動配置 V.S 自動裝配

在這篇文章的開始,艿艿是有點混淆自動配置和自動裝配的概念,後來通過 Google 以後,發現二者是截然不如同的:java

  • 自動配置:是 Spring Boot 提供的,實現經過 jar 包的依賴,可以自動配置應用程序。例如說:咱們引入 spring-boot-starter-web 以後,就自動引入了 Spring MVC 相關的 jar 包,從而自動配置 Spring MVC 。
  • 自動裝配:是 Spring 提供的 IoC 注入方式,具體看看 《Spring 教程 —— Beans 自動裝配》 文檔。

因此,不要和艿艿同樣愚蠢的搞錯落。git

3. 自動裝配原理

胖友能夠直接看 《詳解 Spring Boot 自動配置機制》 文章的 「2、Spring Boot 自動配置」 小節,艿艿以爲寫的挺清晰的。github

下面,咱們即開始正式擼具體的代碼實現了。web

4. @SpringBootApplication

org.springframework.boot.autoconfigure.@SpringBootApplication 註解,基本咱們的 Spring Boot 應用,必定會去有這樣一個註解。而且,經過使用它,不只僅能標記這是一個 Spring Boot 應用,並且可以開啓自動配置的功能。這是爲何呢?spring

😈 @SpringBootApplication 註解,它在 spring-boot-autoconfigure 模塊中。因此,咱們使用 Spring Boot 項目時,若是不想使用自動配置功能,就不用引入它。固然,咱們貌似不太會存在這樣的需求,是吧~數組

@SpringBootApplication 是一個組合註解。代碼以下:網絡

// SpringBootApplication.java

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

/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};

/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};

/**
* Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
* for a type-safe alternative to String-based package names.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};

/**
* Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};

}

下面,咱們來逐個看 @SpringBootApplication 上的每一個註解。app

4.1 @Inherited

Java 自帶的註解。ide

java.lang.annotation.@Inherited 註解,使用此註解聲明出來的自定義註解,在使用此自定義註解時,若是註解在類上面時,子類會自動繼承此註解,不然的話,子類不會繼承此註解。

這裏必定要記住,使用 @Inherited 聲明出來的註解,只有在類上使用時纔會有效,對方法,屬性等其餘無效。

不瞭解的胖友,能夠看看 《關於 Java 註解中元註解 Inherited 的使用詳解》 文章。

4.2 @SpringBootConfiguration

Spring Boot 自定義的註解

org.springframework.boot.@SpringBootConfiguration 註解,標記這是一個 Spring Boot 配置類。代碼以下:

// SpringBootConfiguration.java

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}
  • 能夠看到,它上面繼承自 @Configuration 註解,因此二者功能也一致,能夠將當前類內聲明的一個或多個以 @Bean 註解標記的方法的實例歸入到 Srping 容器中,而且實例名就是方法名。

4.3 @ComponentScan

Spring 自定義的註解

org.springframework.context.annotation.@ComponentScan 註解,掃描指定路徑下的 Component(@Componment@Configuration@Service 等等)。

不瞭解的胖友,能夠看看 《Spring:@ComponentScan 使用》 文章。

4.4 @EnableAutoConfiguration

Spring Boot 自定義的註解

org.springframework.boot.autoconfigure.@EnableAutoConfiguration 註解,用於開啓自動配置功能,是 spring-boot-autoconfigure 項目最核心的註解。代碼以下:

// EnableAutoConfiguration.java

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};

/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};

}
  • org.springframework.boot.autoconfigure.@AutoConfigurationPackage 註解,主要功能自動配置包,它會獲取主程序類所在的包路徑,並將包路徑(包括子包)下的全部組件註冊到 Spring IOC 容器中。代碼以下:

    // AutoConfigurationPackage.java

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {

    }
  • @Import(AutoConfigurationImportSelector.class) 註解部分,是重頭戲的開始。

5. AutoConfigurationImportSelector

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector ,實現 DeferredImportSelector、BeanClassLoaderAware、ResourceLoaderAware、BeanFactoryAware、EnvironmentAware、Ordered 接口,處理 @EnableAutoConfiguration 註解的資源導入。

5.1 getCandidateConfigurations

#getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) 方法,得到符合條件的配置類的數組。代碼以下:

// AutoConfigurationImportSelector.java

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// <1> 加載指定類型 EnableAutoConfiguration 對應的,在 `META-INF/spring.factories` 裏的類名的數組
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), 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;
}
  • <1> 處,調用 #getSpringFactoriesLoaderFactoryClass() 方法,得到要從 META-INF/spring.factories 加載的指定類型爲 EnableAutoConfiguration 類。代碼以下:

    // AutoConfigurationImportSelector.java

    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
    }
  • <1> 處,調用 SpringFactoriesLoader#loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) 方法,加載指定類型 EnableAutoConfiguration 對應的,在 META-INF/spring.factories 裏的類名的數組。看看下圖,胖友相信就明白了:`configurations`

通常來講,和網絡上 Spring Boot 勇於這塊的源碼解析,咱們就能夠結束了。若是單純是爲了瞭解原理 Spring Boot 自動配置的原理,這裏結束也是沒問題的。由於,拿到 Configuration 配置類後,後面的就是 Spring Java Config 的事情了。不瞭解的胖友,能夠看看 《Spring 教程 —— 基於 Java 的配置》 文章。

😜 可是(「可是」同窗,你趕忙坐下),具備倒騰精神的艿艿,以爲仍是繼續瞅瞅 #getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) 方法是怎麼被調用的。因此,咱們來看看調用它的方法調用鏈,以下圖所示:調用鏈

  • ① 處,refresh 方法的調用,咱們在 《精盡 Spring Boot 源碼分析 —— SpringApplication》 中,SpringApplication 啓動時,會調用到該方法。
  • ② 處,#getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) 方法被調用。
  • ③ 處,那麼此處,就是問題的關鍵。代碼以下:

    艿艿:由於我還沒特別完整的擼完 Spring Java Annotations 相關的源碼,因此下面的部分,咱們更可能是看整個調用過程。😈 剛好,胖友也沒看過,哈哈哈哈。

    // ConfigurationClassParser#DeferredImportSelectorGroupingHandler.java

    private final DeferredImportSelector.Group group;

    public Iterable<Group.Entry> getImports() {
    for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
    this.group.process(deferredImport.getConfigurationClass().getMetadata(),
    deferredImport.getImportSelector()); // <1>
    }
    return this.group.selectImports(); // <2>
    }
    • <1> 處,調用 DeferredImportSelector.Group#process(AnnotationMetadata metadata, DeferredImportSelector selector) 方法,處理被 @Import 註解的註解。
    • <2> 處,調用 DeferredImportSelector.Group#this.group.selectImports() 方法,選擇須要導入的。例如:selectImports
      • 這裏,咱們能夠看到須要導入的 Configuration 配置類。
    • 具體 <1> 和 <2> 處,在 「5.3 AutoConfigurationGroup」 詳細解析。

5.2 getImportGroup

#getImportGroup() 方法,得到對應的 Group 實現類。代碼以下:

// AutoConfigurationImportSelector.java

@Override // 實現自 DeferredImportSelector 接口
public Class<? extends Group> getImportGroup() {
return AutoConfigurationGroup.class;
}

5.3 AutoConfigurationGroup

艿艿:注意,從這裏開始後,東西會比較難。由於,涉及的東西會比較多。

AutoConfigurationGroup ,是 AutoConfigurationImportSelector 的內部類,實現 DeferredImportSelector.Group、BeanClassLoaderAware、BeanFactoryAware、ResourceLoaderAware 接口,自動配置的 Group 實現類。

5.3.1 屬性

// AutoConfigurationImportSelector#AutoConfigurationGroup.java

/**
* AnnotationMetadata 的映射
*
* KEY:配置類的全類名
*/
private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();
/**
* AutoConfigurationEntry 的數組
*/
private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();

private ClassLoader beanClassLoader;
private BeanFactory beanFactory;
private ResourceLoader resourceLoader;

/**
* 自動配置的元數據
*/
private AutoConfigurationMetadata autoConfigurationMetadata;
  • entries 屬性,AnnotationMetadata 的映射。其中,KEY 爲 配置類的全類名。在後續咱們將看到的 AutoConfigurationGroup#process(...) 方法中,被進行賦值。例如:`entries`
  • autoConfigurationEntries 屬性,AutoConfigurationEntry 的數組。

    • 其中,AutoConfigurationEntry 是 AutoConfigurationImportSelector 的內部類,自動配置的條目。代碼以下:

      // AutoConfigurationImportSelector#AutoConfigurationEntry.java

      protected static class AutoConfigurationEntry {

      /**
      * 配置類的全類名的數組
      */
      private final List<String> configurations;
      /**
      * 排除的配置類的全類名的數組
      */
      private final Set<String> exclusions;

      // 省略構造方法和 setting/getting 方法

      }
      • 屬性比較簡單。
    • 在後續咱們將看到的 AutoConfigurationGroup#process(...) 方法中,被進行賦值。例如:`autoConfigurationEntries`
  • autoConfigurationMetadata 屬性,自動配置的元數據(Metadata)。

    • 經過 #getAutoConfigurationMetadata() 方法,會初始化該屬性。代碼以下:

      // AutoConfigurationImportSelector#AutoConfigurationGroup.java

      private AutoConfigurationMetadata getAutoConfigurationMetadata() {
      // 不存在,則進行加載
      if (this.autoConfigurationMetadata == null) {
      this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
      }
      // 存在,則直接返回
      return this.autoConfigurationMetadata;
      }
      • 關於 AutoConfigurationMetadataLoader 類,咱們先不去愁。避免,咱們調試的太過深刻。TODO 後續在補充下。
      • 返回的類型是 PropertiesAutoConfigurationMetadata ,比較簡單,胖友點擊 傳送門 瞅一眼便可。
      • 以下是一個返回值的示例:`autoConfigurationEntries`
        • 可能胖友會有點懵逼,這麼多,而且 KEY / VALUE 結果看不懂?不要方,咱們簡單來講下 CouchbaseReactiveRepositoriesAutoConfiguration 配置類。若是它生效,須要 classpath 下有 Bucket、ReactiveCouchbaseRepository、Flux 三個類,因此紅線那個條目,對應的就是 CouchbaseReactiveRepositoriesAutoConfiguration 類上的 @ConditionalOnClass({ Bucket.class, ReactiveCouchbaseRepository.class, Flux.class }) 註解部分。
    • 因此,autoConfigurationMetadata 屬性,用途就是制定配置類(Configuration)的生效條件(Condition)。

5.3.2 process

#process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) 方法,進行處理。代碼以下:

// AutoConfigurationImportSelector#AutoConfigurationGroup.java

@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
// 斷言
Assert.state(
deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
// <1> 得到 AutoConfigurationEntry 對象
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
// <2> 添加到 autoConfigurationEntries 中
this.autoConfigurationEntries.add(autoConfigurationEntry);
// <3> 添加到 entries 中
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
  • annotationMetadata 參數,通常來講是被 @SpringBootApplication 註解的元數據。由於,@SpringBootApplication 組合了 @EnableAutoConfiguration 註解。
  • deferredImportSelector 參數,@EnableAutoConfiguration 註解的定義的 @Import 的類,即 AutoConfigurationImportSelector 對象。
  • <1> 處,調用 AutoConfigurationImportSelector#getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) 方法,得到 AutoConfigurationEntry 對象。詳細解析,見 「5.4 AutoConfigurationEntry」 。由於這塊比較重要,因此先跳過去瞅瞅。
  • <2> 處,添加到 autoConfigurationEntries 中。
  • <3> 處,添加到 entries 中。

5.3.3 selectImports

#selectImports() 方法,得到要引入的配置類。代碼以下:

// AutoConfigurationImportSelector#AutoConfigurationGroup.java

@Override
public Iterable<Entry> selectImports() {
// <1> 若是爲空,則返回空數組
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
}
// <2.1> 得到 allExclusions
Set<String> allExclusions = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getExclusions)
.flatMap(Collection::stream).collect(Collectors.toSet());
// <2.2> 得到 processedConfigurations
Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getConfigurations)
.flatMap(Collection::stream)
.collect(Collectors.toCollection(LinkedHashSet::new));
// <2.3> 從 processedConfigurations 中,移除排除的
processedConfigurations.removeAll(allExclusions);
// <3> 處理,返回結果
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()) // <3.1> 排序
.stream()
.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName)) // <3.2> 建立 Entry 對象
.collect(Collectors.toList()); // <3.3> 轉換成 List
}
  • <1> 處,若是爲空,則返回空數組。
  • <2.1><2.2><2.3> 處,得到要引入的配置類集合。😈 比較奇怪的是,上面已經作過一次移除的處理,這裏又作一次。不過,沒多大關係,能夠先無視。
  • <3> 處,處理,返回結果。

    • <3.1> 處,調用 #sortAutoConfigurations(Set<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) 方法,排序。代碼以下:

      // AutoConfigurationImportSelector#AutoConfigurationGroup.java

      private List<String> sortAutoConfigurations(Set<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
      return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata).getInPriorityOrder(configurations);
      }
      • 具體的排序邏輯,胖友本身看。實際上,仍是涉及哪些,例如說 @Order 註解。
    • <3.2> 處,建立 Entry 對象。
    • <3.3> 處,轉換成 List 。結果以下圖:結果

艿艿:略微有點艱難的過程。不過回過頭來,其實也沒啥特別複雜的邏輯。是不,胖友~

5.4 getAutoConfigurationEntry

艿艿:這是一個關鍵方法。由於會調用到,咱們會在 「5.1 getCandidateConfigurations」 的方法。

#getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) 方法,得到 AutoConfigurationEntry 對象。代碼以下:

// AutoConfigurationImportSelector.java

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
// <1> 判斷是否開啓。如未開啓,返回空數組。
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// <2> 得到註解的屬性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// <3> 得到符合條件的配置類的數組
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// <3.1> 移除重複的配置類
configurations = removeDuplicates(configurations);
// <4> 得到須要排除的配置類
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// <4.1> 校驗排除的配置類是否合法
checkExcludedClasses(configurations, exclusions);
// <4.2> 從 configurations 中,移除須要排除的配置類
configurations.removeAll(exclusions);
// <5> 根據條件(Condition),過濾掉不符合條件的配置類
configurations = filter(configurations, autoConfigurationMetadata);
// <6> 觸發自動配置類引入完成的事件
fireAutoConfigurationImportEvents(configurations, exclusions);
// <7> 建立 AutoConfigurationEntry 對象
return new AutoConfigurationEntry(configurations, exclusions);
}

這裏每一步都是細節的方法,因此會每個方法,都會是引導到對應的小節的方法。

雖然有點長,可是很不復雜。簡單的來講,加載符合條件的配置類們,而後移除須要排除(exclusion)的。

  • <1> 處,調用 #isEnabled(AnnotationMetadata metadata) 方法,判斷是否開啓。如未開啓,返回空數組。詳細解析,見 「5.4.1 isEnabled」 。
  • <2> 處,調用 #getAttributes(AnnotationMetadata metadata) 方法,得到註解的屬性。詳細解析,見 「5.4.2 getAttributes」 。
  • 【重要】<3> 處,調用 #getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) 方法,得到符合條件的配置類的數組。

    嘻嘻,到達此書以後,整個細節是否是就串起來了!

    • <3.1> 處,調用 #removeDuplicates(List<T> list) 方法,移除重複的配置類。代碼以下:

      // AutoConfigurationImportSelector.java

      protected final <T> List<T> removeDuplicates(List<T> list) {
      return new ArrayList<>(new LinkedHashSet<>(list));
      }
      • 簡單粗暴
  • <4> 處,調用 #getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) 方法,得到須要排除的配置類。詳細解析,見 「5.4.3 getExclusions」 。

    • <4.1> 處,調用 #checkExcludedClasses(List<String> configurations, Set<String> exclusions) 方法,校驗排除的配置類是否合法。詳細解析,見 「5.4.4 checkExcludedClasses」 。
    • <4.2> 處,從 configurations 中,移除須要排除的配置類。
  • <5> 處,調用 #filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) 方法,根據條件(Condition),過濾掉不符合條件的配置類。詳細解析,見 《精盡 Spring Boot 源碼分析 —— Condition》 文章。

  • <6> 處,調用 #fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) 方法,觸發自動配置類引入完成的事件。詳細解析,見 「5.4.5 fireAutoConfigurationImportEvents」 。
  • <7> 處,建立 AutoConfigurationEntry 對象。

整個 「5.4 getAutoConfigurationEntry」 看完後,胖友請跳回 「5.3.3 selectImports」 。

5.4.1 isEnabled

#isEnabled(AnnotationMetadata metadata) 方法,判斷是否開啓自動配置。代碼以下:

// AutoConfigurationImportSelector.java

protected boolean isEnabled(AnnotationMetadata metadata) {
// 判斷 "spring.boot.enableautoconfiguration" 配置判斷,是否開啓自動配置。
// 默認狀況下(未配置),開啓自動配置。
if (getClass() == AutoConfigurationImportSelector.class) {
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
}

5.4.2 getAttributes

#getAttributes(AnnotationMetadata metadata) 方法,得到註解的屬性。代碼以下:

// AutoConfigurationImportSelector.java

protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
String name = getAnnotationClass().getName();
// 得到註解的屬性
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
// 斷言
Assert.notNull(attributes,
() -> "No auto-configuration attributes found. Is "
+ metadata.getClassName() + " annotated with "
+ ClassUtils.getShortName(name) + "?");
return attributes;
}
  • 注意,此處 getAnnotationClass().getName() 返回的是 @EnableAutoConfiguration 註解,因此這裏返回的註解屬性,只能是 exclude 和 excludeName 這兩個。
  • 舉個例子,假設 Spring 應用上的註解以下:

    @SpringBootApplication(exclude = {SpringApplicationAdminJmxAutoConfiguration.class},
    scanBasePackages = "cn.iocoder")
    • 返回的結果,以下圖:`attributes`

5.4.3 getExclusions

#getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) 方法,得到須要排除的配置類。代碼以下:

// AutoConfigurationImportSelector.java

protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
Set<String> excluded = new LinkedHashSet<>();
// 註解上的 exclude 屬性
excluded.addAll(asList(attributes, "exclude"));
// 註解上的 excludeName 屬性
excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
// 配置文件的 spring.autoconfigure.exclude 屬性
excluded.addAll(getExcludeAutoConfigurationsProperty());
return excluded;
}
  • 一共有三種方式,配置排除屬性。
  • 該方法會調用以下的方法,比較簡單,胖友本身瞅瞅。

    // AutoConfigurationImportSelector.java

    private List<String> getExcludeAutoConfigurationsProperty() {
    // 通常來講,會走這塊的邏輯
    if (getEnvironment() instanceof ConfigurableEnvironment) {
    Binder binder = Binder.get(getEnvironment());
    return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class).map(Arrays::asList).orElse(Collections.emptyList());
    }
    String[] excludes = getEnvironment().getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
    return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
    }

    protected final List<String> asList(AnnotationAttributes attributes, String name) {
    String[] value = attributes.getStringArray(name);
    return Arrays.asList(value);
    }

5.4.4 checkExcludedClasses

#checkExcludedClasses(List<String> configurations, Set<String> exclusions) 方法,校驗排除的配置類是否合法。代碼以下:

// AutoConfigurationImportSelector.java

private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
// 得到 exclusions 不在 invalidExcludes 的集合,添加到 invalidExcludes 中
List<String> invalidExcludes = new ArrayList<>(exclusions.size());
for (String exclusion : exclusions) {
if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) // classpath 存在該類
&& !configurations.contains(exclusion)) { // configurations 不存在該類
invalidExcludes.add(exclusion);
}
}
// 若是 invalidExcludes 非空,拋出 IllegalStateException 異常
if (!invalidExcludes.isEmpty()) {
handleInvalidExcludes(invalidExcludes);
}
}

/**
* Handle any invalid excludes that have been specified.
* @param invalidExcludes the list of invalid excludes (will always have at least one
* element)
*/
protected void handleInvalidExcludes(List<String> invalidExcludes) {
StringBuilder message = new StringBuilder();
for (String exclude : invalidExcludes) {
message.append("\t- ").append(exclude).append(String.format("%n"));
}
throw new IllegalStateException(String.format("The following classes could not be excluded because they are"
+ " not auto-configuration classes:%n%s", message));
}
  • 不合法的定義,exclusions 存在於 classpath 中,可是不存在 configurations 。這樣作的目的是,若是不存在的,就不要去排除啦!
  • 代碼比較簡單,胖友本身瞅瞅便可。

5.4.5 fireAutoConfigurationImportEvents

#fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) 方法,觸發自動配置類引入完成的事件。代碼以下:

// AutoConfigurationImportSelector.java

private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
// <1> 加載指定類型 AutoConfigurationImportListener 對應的,在 `META-INF/spring.factories` 裏的類名的數組。
List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
if (!listeners.isEmpty()) {
// <2> 建立 AutoConfigurationImportEvent 事件
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
// <3> 遍歷 AutoConfigurationImportListener 監聽器們,逐個通知
for (AutoConfigurationImportListener listener : listeners) {
// <3.1> 設置 AutoConfigurationImportListener 的屬性
invokeAwareMethods(listener);
// <3.2> 通知
listener.onAutoConfigurationImportEvent(event);
}
}
}
  • <1> 處,調用 #getAutoConfigurationImportListeners() 方法,加載指定類型 AutoConfigurationImportListener 對應的,在 META-INF/spring.factories 裏的類名的數組。例如:`listeners`
  • <2> 處,建立 AutoConfigurationImportEvent 事件。
  • <3> 處,遍歷 AutoConfigurationImportListener 監聽器們,逐個通知。

    • <3.1> 處,調用 #invokeAwareMethods(Object instance) 方法,設置 AutoConfigurationImportListener 的屬性。代碼以下:

      // AutoConfigurationImportSelector.java

      private void invokeAwareMethods(Object instance) {
      // 各類 Aware 屬性的注入
      if (instance instanceof Aware) {
      if (instance instanceof BeanClassLoaderAware) {
      ((BeanClassLoaderAware) instance).setBeanClassLoader(this.beanClassLoader);
      }
      if (instance instanceof BeanFactoryAware) {
      ((BeanFactoryAware) instance).setBeanFactory(this.beanFactory);
      }
      if (instance instanceof EnvironmentAware) {
      ((EnvironmentAware) instance).setEnvironment(this.environment);
      }
      if (instance instanceof ResourceLoaderAware) {
      ((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader);
      }
      }
      }
      • 各類 Aware 屬性的注入。
    • <3.2> 處,調用 AutoConfigurationImportListener#onAutoConfigurationImportEvent(event) 方法,通知監聽器。目前只有一個 ConditionEvaluationReportAutoConfigurationImportListener 監聽器,沒啥邏輯,有興趣本身看哈。

6. AutoConfigurationPackages

org.springframework.boot.autoconfigure.AutoConfigurationPackages ,自動配置所在的包名。可能這麼解釋有點怪怪的,咱們來看下官方註釋:

Class for storing auto-configuration packages for reference later (e.g. by JPA entity scanner).
  • 簡單來講,就是將使用 @AutoConfigurationPackage 註解的類所在的包(package),註冊成一個 Spring IoC 容器中的 Bean 。醬紫,後續有其它模塊須要使用,就能夠經過得到該 Bean ,從而得到所在的包。例如說,JPA 模塊,須要使用到。

是否是有點神奇,艿艿也以爲。

6.1 Registrar

Registrar ,是 AutoConfigurationPackages 的內部類,實現 ImportBeanDefinitionRegistrar、DeterminableImports 接口,註冊器,用於處理 @AutoConfigurationPackage 註解。代碼以下:

// AutoConfigurationPackages#Registrar.java

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName()); // <X>
}

@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}

}
  • PackageImport 是 AutoConfigurationPackages 的內部類,用於得到包名。代碼以下:

    // AutoConfigurationPackages#Registrar.java

    private static final class PackageImport {

    /**
    * 包名
    */
    private final String packageName;

    PackageImport(AnnotationMetadata metadata) {
    this.packageName = ClassUtils.getPackageName(metadata.getClassName());
    }

    public String getPackageName() {
    return this.packageName;
    }

    @Override
    public boolean equals(Object obj) {
    if (obj == null || getClass() != obj.getClass()) {
    return false;
    }
    return this.packageName.equals(((PackageImport) obj).packageName);
    }

    @Override
    public int hashCode() {
    return this.packageName.hashCode();
    }

    @Override
    public String toString() {
    return "Package Import " + this.packageName;
    }

    }
    • 以下是一個示例:PackageImport
  • <X> 處,調用 #register(BeanDefinitionRegistry registry, String... packageNames) 方法,註冊一個用於存儲報名(package)的 Bean 到 Spring IoC 容器中。詳細解析,見 「6.2 register」 。

6.2 register

#register(BeanDefinitionRegistry registry, String... packageNames) 方法,註冊一個用於存儲報名(package)的 Bean 到 Spring IoC 容器中。代碼以下:

// AutoConfigurationPackages.java

private static final String BEAN = AutoConfigurationPackages.class.getName();

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
// <1> 若是已經存在該 BEAN ,則修改其包(package)屬性
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
// <2> 若是不存在該 BEAN ,則建立一個 Bean ,並進行註冊
} else { GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
  • 註冊的 BEAN 的類型,爲 BasePackages 類型。它是 AutoConfigurationPackages 的內部類。代碼以下:

    // AutoConfigurationPackages#BasePackages.java

    static final class BasePackages {

    private final List<String> packages;

    private boolean loggedBasePackageInfo;

    BasePackages(String... names) {
    List<String> packages = new ArrayList<>();
    for (String name : names) {
    if (StringUtils.hasText(name)) {
    packages.add(name);
    }
    }
    this.packages = packages;
    }

    public List<String> get() {
    if (!this.loggedBasePackageInfo) {
    if (this.packages.isEmpty()) {
    if (logger.isWarnEnabled()) {
    logger.warn("@EnableAutoConfiguration was declared on a class "
    + "in the default package. Automatic @Repository and "
    + "@Entity scanning is not enabled.");
    }
    } else {
    if (logger.isDebugEnabled()) {
    String packageNames = StringUtils
    .collectionToCommaDelimitedString(this.packages);
    logger.debug("@EnableAutoConfiguration was declared on a class "
    + "in the package '" + packageNames
    + "'. Automatic @Repository and @Entity scanning is "
    + "enabled.");
    }
    }
    this.loggedBasePackageInfo = true;
    }
    return this.packages;
    }

    }
    • 就是一個有 packages 屬性的封裝類。
  • <1> 處,若是已經存在該 BEAN ,則修改其包(package)屬性。而合併 package 的邏輯,經過 #addBasePackages(ConstructorArgumentValues constructorArguments, String[] packageNames) 方法,進行實現。代碼以下:
    // AutoConfigurationPackages.java

    private static String[] addBasePackages(ConstructorArgumentValues constructorArguments, String[] packageNames) {
    // 得到已存在的
    String[] existing = (String[]) constructorArguments.getIndexedArgumentValue(0, String[].class).getValue();
    // 進行合併
    Set<String> merged = new LinkedHashSet<>();
    merged.addAll(Arrays.asList(existing));
    merged.addAll(Arrays.asList(packageNames));
    return StringUtils.toStringArray(merged);
    }
  • <2> 處,若是不存在該 BEAN ,則建立一個 Bean ,並進行註冊。

6.3 has

#has(BeanFactory beanFactory) 方法,判斷是否存在該 BEAN 在傳入的容器中。代碼以下:

// AutoConfigurationPackages.java

public static boolean has(BeanFactory beanFactory) {
return beanFactory.containsBean(BEAN) && !get(beanFactory).isEmpty();
}

6.4 get

#get(BeanFactory beanFactory) 方法,得到 BEAN 。代碼以下:

// AutoConfigurationPackages.java

public static List<String> get(BeanFactory beanFactory) {
try {
return beanFactory.getBean(BEAN, BasePackages.class).get();
} catch (NoSuchBeanDefinitionException ex) {
throw new IllegalStateException("Unable to retrieve @EnableAutoConfiguration base packages");
}
}
相關文章
相關標籤/搜索