SpringBoot的自动配置(2)-@EnableAutoConfiguration自动装配

上一节讲了自动配置中@ComponentScan是如何将启动类所在的父目录下所有带@Component注解的类注册到beanfactory中的
springboot自动装配另一个重要的部分就是@EnableAutoConfiguration自动装配DispatcherServlet,DataSource等固定配置。下面从源码角度来分析其是如何实现的:

先来看看@EnableAutoConfiguration注解

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

@EnableAutoConfiguration注解自动装配的关键就是其@Import的EnableAutoConfigurationImportSelector类,下面就分析该类是是如何起作用的


1、@Import注解解析

从上一节的doProcessConfigurationClass()函数接着说起,@ComponentScan注解解析完之后,就接着解析@Import标签,从这也就可以看出,EnableAutoConfigurationImportSelector类就是通过对@import注解解析从而被spring boot获取并且使用的

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
      throws IOException {
   .......
   // Process any @ComponentScan annotations
   Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
         sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
   if (!componentScans.isEmpty() &&
         !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
      for (AnnotationAttributes componentScan : componentScans) {
         // 扫描组件,向容器中注册
         Set<BeanDefinitionHolder> scannedBeanDefinitions =
               this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
         // 遍历bean,递归执行parse,查看其中是否还有用户自定义的配置类
         for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
            if (ConfigurationClassUtils.checkConfigurationClassCandidate(
                  holder.getBeanDefinition(), this.metadataReaderFactory)) {
               parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
            }
         }
      }
   }

   // Process any @Import annotations
   processImports(configClass, sourceClass, getImports(sourceClass), true);
   .....
}

先看看getImports()函数,它获取了启动类下所有@Import注解import的类

private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
   Set<SourceClass> imports = new LinkedHashSet<SourceClass>();
   Set<SourceClass> visited = new LinkedHashSet<SourceClass>();
   //获取sourceClass注解下所有Import的类
   collectImports(sourceClass, imports, visited);
   return imports;
}

可以看到EnableAutoConfigurationImportSelector类被获取到了
在这里插入图片描述
再看processImports()函数,挑主要的代码看看

.....
for (SourceClass candidate : importCandidates) {
   if (candidate.isAssignable(ImportSelector.class)) {
      // Candidate class is an ImportSelector -> delegate to it to determine imports
      Class<?> candidateClass = candidate.loadClass();
      //实例化selector
      ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
      ParserStrategyUtils.invokeAwareMethods(
            selector, this.environment, this.resourceLoader, this.registry);
      if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
        //将实例化的selector,加入到集合中备用
          this.deferredImportSelectors.add(
               new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
      }
      else {
         String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
         Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
         processImports(configClass, currentSourceClass, importSourceClasses, false);
      }
   }
}
........

从中可以看到EnableAutoConfigurationImportSelector类实例化的对象已经被spring boot获取备用了
在这里插入图片描述


2、开始执行springboot的默认自动配置逻辑

继续回到ConfigurationClassParser中的parse方法,回到该方法的最后一步:

public void parse(Set<BeanDefinitionHolder> configCandidates) {
		//...
		//开始执行默认配置
		processDeferredImportSelectors();
	}

跟进processDeferredImportSelectors()方法

private void processDeferredImportSelectors() {
    //这就是上面的存储selector的集合
   List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
   this.deferredImportSelectors = null;
   Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);

   for (DeferredImportSelectorHolder deferredImport : deferredImports) {
      ConfigurationClass configClass = deferredImport.getConfigurationClass();
      try {
          //获取配置类的类名
         String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
         //递归解析配置类
          processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
      }
      catch (BeanDefinitionStoreException ex) {
         throw ex;
      }
      catch (Throwable ex) {
         throw new BeanDefinitionStoreException(
               "Failed to process import candidates for configuration class [" +
               configClass.getMetadata().getClassName() + "]", ex);
      }
   }
}
(1)获取配置类类名

跟一下获取配置类类名的过程,最后追到了loadFactoryNames()方法,是不是很熟悉,在之前的注册监听器的那一节,监听器也是通过这个方法从固定的spring.factories 获取类名的

public String[] selectImports(AnnotationMetadata annotationMetadata) {
.......
    //获取默认的配置类
      List<String> configurations = getCandidateConfigurations(annotationMetadata,
            attributes);
 .......
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
      AnnotationAttributes attributes) {
  //获取默认配置类
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
         getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
......
}

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
   String factoryClassName = factoryClass.getName();
   try {
      Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      List<String> result = new ArrayList<String>();
      while (urls.hasMoreElements()) {
         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;
   }
}

springBoot为我们提供的所有配置类如下,大概100多个:
在这里插入图片描述
获取完之后,spring boot自动筛选出适合的配置类(根据该类是否存在,即相应的jar包是否引入了)
从中我们可以看到熟悉的DispatherServlet,Datasource
在这里插入图片描述

(2) 根据类名解析配置类

这个processImport()方法上面才见过,上述是为了添加selector,这里是用来解析配置类的

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
      Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException {

 for (SourceClass candidate : importCandidates) {
   .......
    else {
       // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
       // process it as an @Configuration class
       this.importStack.registerImport(
             currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
       //解析配置类
       processConfigurationClass(candidate.asConfigClass(configClass));
    }
 }

}

关于自动配置类是如何配置类,我在“SpringBoot之application.properties的加载”中已经讲过,再次用到这个doProcessConfigurationClass函数,这次是为了解析自动配置类中@Bean标签注解的方法

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
      throws IOException {
.......
   // Process individual @Bean methods
   Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
   for (MethodMetadata methodMetadata : beanMethods) {
      configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
   }
......
}

我们以DataSourceAutoConfiguration为例,其中有一个方法

@Configuration
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Hikari.class,
      DataSourceConfiguration.Dbcp.class, DataSourceConfiguration.Dbcp2.class,
      DataSourceConfiguration.Generic.class })
@SuppressWarnings("deprecation")
protected static class PooledDataSourceConfiguration {

}

跟踪其中的@Import注解,比如DataSourceConfiguration.Tomcat.class,这中的@Bean就是自动配置DataSource的beanMethod,其中@ConfigurationProperties(prefix = “spring.datasource.tomcat”)标签还可以使用application.properties中以"spring.datasource.tomcat"为前缀的配置

@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.tomcat.jdbc.pool.DataSource", matchIfMissing = true)
static class Tomcat extends DataSourceConfiguration {

   @Bean
   @ConfigurationProperties(prefix = "spring.datasource.tomcat")
   public org.apache.tomcat.jdbc.pool.DataSource dataSource(
         DataSourceProperties properties) {
      org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(
            properties, org.apache.tomcat.jdbc.pool.DataSource.class);
      DatabaseDriver databaseDriver = DatabaseDriver
            .fromJdbcUrl(properties.determineUrl());
      String validationQuery = databaseDriver.getValidationQuery();
      if (validationQuery != null) {
         dataSource.setTestOnBorrow(true);
         dataSource.setValidationQuery(validationQuery);
      }
      return dataSource;
   }
}

执行完成之后,可以看到beanfatory中已经有了这些类的beanDefinition

在这里插入图片描述

至此,自动装配的内容已经介绍完了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值