Spring Boot 中的 @EnableAutoConfiguration 是如何处理的?

共 10824字,需浏览 22分钟

 ·

2020-07-31 17:18

点击上方蓝色“程序猿DD”,选择“设为星标”

回复“资源”获取独家整理的学习资料!

作者 | 温安适

来源 | https://my.oschina.net/floor/blog/4354771
赠书:聊聊「分布式架构」那些事儿

引言

工作中,我们直接或间接的,用到@EnableAutoConfiguration注解。

今天,我们就聊聊@EnableAutoConfiguration的处理逻辑。

找核心方法

@Enable开头的的注解,上一般都有@Import用于指定要注解的逻辑实现类。

@EnableAutoConfiguration上的@Import,导入的是 AutoConfigurationImportSelector。

AutoConfigurationImportSelector就是要找的入口类。 类关系如下:

img

Aware系列都是用于注入响应的资源,Ordered用于排序。

值得关注的是 DeferredImportSelector,查看其类注释,简要翻译如下:

importselector的变体,在所有@Configuration bean之后运行,可以实现Ordered进行排序。

提供{getImportGroup(),它可以跨不同的选择器提供额外的排序和过滤逻辑。

DeferredImportSelector保证在所有@Configuration加载之后执行,也就说,如果有相关配置类已加载,则可以跳过自动装配类。

DeferredImportSelector是如何保证在@Configuration bean加载之后执行的呢???

带着这个疑问,我查看了ConfigurationClassPostProcessor#processConfigBeanDefinitions

(至于为什么要查看这个方法,请看上一篇 @Enable驱动原理)

浏览过程如下:

img

概要逻辑如下:

\1. ImportSelector的解析在ConfigurationClassParser#processImports中处理

在其中this.deferredImportSelectorHandler.handle(..)j将DeferredImportSelector放入队列,延后处理。

\2. DeferredImportSelector处理逻辑在

ConfigurationClassParser#parse中的this.deferredImportSelectorHandler.process()中。

浏览this.deferredImportSelectorHandler.process()代码;

img

DeferredImportSelectorGrouping#getImports的代码如下:

private static class DeferredImportSelectorGrouping {
    private final DeferredImportSelector.Group group;
   public Iterable getImports() {
   for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
      this.group.process(deferredImport.getConfigurationClass().getMetadata(),
            deferredImport.getImportSelector());
   }
   return this.group.selectImports();
}     

}

这里需要关注的是this.group.process,this.group.selectImports2个方法。

也就是AutoConfigurationImportSelector.AutoConfigurationGroup的process,selectImports就是我们需要关注的核心方法。

逐个分析

process

public class AutoConfigurationImportSelector {
private static class AutoConfigurationGroup{
    //省略其他代码
  private final Map entries = new LinkedHashMap<>();  
@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()));
   AutoConfigurationEntry autoConfigurationEntry = 
   ((AutoConfigurationImportSelector) deferredImportSelector)
         .getAutoConfigurationEntry(getAutoConfigurationMetadata(),
                          annotationMetadata);
   this.autoConfigurationEntries.add(autoConfigurationEntry);
   for (String importClassName : autoConfigurationEntry.getConfigurations()) {
      this.entries.putIfAbsent(importClassName, annotationMetadata);
   }
}
}

概要逻辑:

1.getAutoConfigurationMetadata() 加载autoConfigurationMetadata,

2.getAutoConfigurationEntry()根据autoConfigurationMetadata获得AutoConfigurationEntry

3.通过AutoConfigurationEntry ,获得要导入的类的名称,存入内部的 autoConfigurationEntries中

1. getAutoConfigurationMetadata() 加载autoConfigurationMetadata

public class AutoConfigurationImportSelector {
private static class AutoConfigurationGroup{
private AutoConfigurationMetadata getAutoConfigurationMetadata() {
   if (this.autoConfigurationMetadata == null) {
      this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
   }
   return this.autoConfigurationMetadata;
}
}
}

核心就是AutoConfigurationMetadataLoader.loadMetadata 查看其源码如下:

final class AutoConfigurationMetadataLoader {
   protected static final String PATH
            = "META-INF/" + "spring-autoconfigure-metadata.properties";
   private AutoConfigurationMetadataLoader() {
   }
   public static AutoConfigurationMetadata loadMetadata
                                   (ClassLoader classLoader) {
      return loadMetadata(classLoader, PATH);
   }
   static AutoConfigurationMetadata loadMetadata
                       (ClassLoader classLoader, String path) {
      try {
         Enumeration urls = (classLoader != null) 
                         ? classLoader.getResources(path)
                           : ClassLoader.getSystemResources(path);
         Properties properties = new Properties();
         while (urls.hasMoreElements()) {
            properties.putAll(
                PropertiesLoaderUtils.loadProperties(
                                new UrlResource(urls.nextElement())));
         }
         return loadMetadata(properties);
      }
      catch (IOException ex) {
         throw new
          IllegalArgumentException(
             "Unable to load @ConditionalOnClass location [" + path + "]",
                                                                      ex);
      }
   }
   //省略部分代码
 }   

加载autoConfigurationMetadata,就是读取META-INF/spring-autoconfigure-metadata.properties的配置文件,转换为autoConfigurationMetadata对象。

2.根据autoConfigurationMetadata获得AutoConfigurationEntry

getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata)的实现如下

public class AutoConfigurationImportSelector{
    //省略其他代码
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   List configurations = getCandidateConfigurations(annotationMetadata, attributes);
   configurations = removeDuplicates(configurations);
   Set exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
   configurations = filter(configurations, autoConfigurationMetadata);
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}
}

概要逻辑:

  • getAttributes获取注解的属性
  • getCandidateConfiguration获取候选装配组件
  • removeDuplicates删除重复的配置项
  • getExclusions 获取排除的组件
  • spring.autoconfigure.exclude,exclude,excludeName对应的值存储到set中
  • checkExcludedClasses 当前类在classLoader中,但是不在候选列表中内抛出异常
  • configurations.removeAll(exclusions)移除需要排除的配置项
  • filter 过滤不满足条件的自动装配组件
  • fireAutoConfigurationImportEvents 发送@EnableAutoConfiguration的自动装配事件

getCandidateConfiguration获取候选装配组件

protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   List 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;
}

查看 SpringFactoriesLoader源码如下:

public final class SpringFactoriesLoader {
    //省略部分代码
 public static final String FACTORIES_RESOURCE_LOCATION 
                                     = "META-INF/spring.factories";
 
 public static List loadFactoryNames(
             Class factoryClass, @Nullable ClassLoader classLoader) {
   String factoryClassName = factoryClass.getName();
   return loadSpringFactories(classLoader)
                   .getOrDefault(factoryClassName, Collections.emptyList());
}

private static Map> loadSpringFactories(
                                @Nullable ClassLoader classLoader) {
   MultiValueMap result = cache.get(classLoader);
   if (result != null) {
      return result;
   }
   try {
      Enumeration urls = (classLoader != null ?
            classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      result = new LinkedMultiValueMap<>();
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         UrlResource resource = new UrlResource(url);
         Properties properties = PropertiesLoaderUtils
                                         .loadProperties(resource);
         for (Map.Entry entry : properties.entrySet()) {
            String factoryClassName = ((String) entry.getKey()).trim();
            for (String factoryName : StringUtils
                        .commaDelimitedListToStringArray(
                                    (String) entry.getValue())) {
               result.add(factoryClassName, factoryName.trim());
            }
         }
      }
      cache.put(classLoader, result);
      return result;
   }
   catch (IOException ex) {
      throw new IllegalArgumentException(
                  "Unable to load factories from location [" +
                            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
}                
}    

读取指定ClassLoader下的所有的,META-INF/spring.factories资源内容,合并一个key为全类名,Value为实现类名列表的Map,从Map中找到指定的key对应的实现类全类名列表

filter 过滤不满足条件的自动装配组件

public class AutoConfigurationImportSelector{
private List filter(List configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
   long startTime = System.nanoTime();
   String[] candidates = StringUtils.toStringArray(configurations);
   boolean[] skip = new boolean[candidates.length];
   boolean skipped = false;
   for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
      invokeAwareMethods(filter);
      boolean[] match = filter.match(candidates, autoConfigurationMetadata);
      for (int i = 0; i < match.length; i++) {
         if (!match[i]) {
            skip[i] = true;
            candidates[i] = null;
            skipped = true;
         }
      }
   }
   if (!skipped) {
      return configurations;
   }
   List result = new ArrayList<>(candidates.length);
   for (int i = 0; i < candidates.length; i++) {
      if (!skip[i]) {
         result.add(candidates[i]);
      }
   }
   if (logger.isTraceEnabled()) {
      int numberFiltered = configurations.size() - result.size();
      logger.trace("Filtered " + numberFiltered + " auto configuration class in "
            + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
   }
   return new ArrayList<>(result);
}
}

AutoConfigurationImportFilter#match没有匹配上的过滤掉

查看getAutoConfigurationImportFilters()源码如下:

protected List getAutoConfigurationImportFilters() {
   return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}

SpringFactoriesLoader.loadFactories(...),调用SpringFactoriesLoader.loadFactoryNames(...)后,将列表中的逐个实例化,排序后返回。

查看spring.factories

img

AutoConfigurationImportFilter的实现类,有

OnClassCondition类,onBeanCondition,OnWebApplicationCondition等。

扩展-查阅OnClassCondition的match方法

img

OnClassCondition代码大致逻辑如下:

OnClassCondition#match调用了autoConfigurationMetadata.getSet获取当前配置类的ConditionOnClass属性。

举例说明OnClassCondition#getOutcomes

例如:

加载AConfigurationClass,AConfigurationClass的ConditionOnClass=XXX,

如果XXX不在当前classloader下,排除 AConfigurationClass

selectImports

public class AutoConfigurationImportSelector{
private static class AutoConfigurationGroup{
@Override
public Iterable selectImports() {
   if (this.autoConfigurationEntries.isEmpty()) {
      return Collections.emptyList();
   }
   Set allExclusions = this.autoConfigurationEntries.stream()
         .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
   Set processedConfigurations = 
   this.autoConfigurationEntries.stream()
         .map(AutoConfigurationEntry::getConfigurations)
         .flatMap(Collection::stream)
         .collect(Collectors.toCollection(LinkedHashSet::new));
   processedConfigurations.removeAll(allExclusions);

   return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
         .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
         .collect(Collectors.toList());
}
}
}
}
}

概要逻辑如下

排除不必要的项

sortAutoConfigurations排序自动配置项。

查看sortAutoConfigurations

public class AutoConfigurationImportSelector{
private static class AutoConfigurationGroup{
private List sortAutoConfigurations(Set configurations,
      AutoConfigurationMetadata autoConfigurationMetadata) {
   return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata)
         .getInPriorityOrder(configurations);
}
}
}

核心在AutoConfigurationSorter#getInPriorityOrder中

class AutoConfigurationSorter {
    //省略其他代码
public List getInPriorityOrder(Collection classNames) {
   AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory,
         this.autoConfigurationMetadata, classNames);
   List orderedClassNames = new ArrayList<>(classNames);
   // Initially sort alphabetically
   Collections.sort(orderedClassNames);
   // Then sort by order
   orderedClassNames.sort((o1, o2) -> {
      int i1 = classes.get(o1).getOrder();
      int i2 = classes.get(o2).getOrder();
      return Integer.compare(i1, i2);
   });
   // Then respect @AutoConfigureBefore @AutoConfigureAfter
   orderedClassNames = sortByAnnotation(classes, orderedClassNames);
   return orderedClassNames;
}
}

排序规则如下:

按照字母顺序加载,否则先按AutoCOnfigureOrder,再按@AutoConfigureBefore,@AutoConfigureAfter进行排序。

注:

AutoCOnfigurationMetadata(META-INF/spring-autoconfigure-metadata.properties)中包含AutoConfigureOrder,AutoConfigureBefore,AutoConfigureAfter信息

名称注释是否推荐
@AutoCOnfigureOrder绝对自动装配顺序
@AutoConfigureBefore,@AutoConfigureAfter相对自动装配顺序,建议使用name属性

整体流程到这里就看完了,但是自定义配置如何覆盖自动装配的呢?

查漏补缺

为了查找自定义配置如何覆盖自动装配,我再次翻阅代码

img

查看shouldSkip,结合各种Conditional注解进行过滤

@Conditional 是个多个条件注解的元注解,例如:@ConditionalOnMissingBean,@ConditionalOnClass等。

具体逻辑,就不在这里赘述了,如果想查阅,可以查看org.springframework.boot.autoconfigure.condition.OnClassCondition这个类,它是@ConditionalOnClass的逻辑处理类。

总结

  1. @EnableAutoConfiguration的核心处理类是AutoConfigurationImportSelector
  2. AutoConfigurationImportSelector实现了DeferredImportSelector
  3. ImportSelector的解析在ConfigurationClassParser#processImports中处理在其中this.deferredImportSelectorHandler.handle(..)j将DeferredImportSelector放入队列,延后处理。DeferredImportSelector处理逻辑在ConfigurationClassParser#parse中的this.deferredImportSelectorHandler.process()中。
  4. AutoConfigurationImportSelector.AutoConfigurationGroup的process,selectImports就是我们需要关注的核心方法。
  5. ConfigurationClassParser#doProcessConfigurationClass中的!this.conditionEvaluator.shouldSkip(...) 结合了各种Condition注解,实现了自定义配置覆盖自动装配。



往期推荐

BeanUtils 是用 Spring 的还是 Apache 的好?

MySQL 如何优化 CPU 消耗?

Spring Boot 中 @EnableXXX 注解的驱动逻辑

有比 ReadWriteLock更快的锁?

认真看看, 以后写 SQL 就爽多了:MyBatis 动态 SQL:


扫一扫,关注我

一起学习,一起进步

浏览 35
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报