SpringBoot 源码解读系列(3)[email protected]注解的源码处理分析

  一般我们创建一个SpringBoot项目,其启动类加@SpringBootApplication注解就能直接启动。

@SpringBootApplication
public class SpringbootdemoApplication {

   public static void main(String[] args) {
      SpringApplication.run(SpringbootdemoApplication.class, args);
   }
}

  上面就是一个启动demo。SpringApplication.run的入参就是表示Class(SpringbootdemoApplication),但这里其是主要就是需要扫描到其上面添加的@SpringBootApplication注解,下面我们来分析下@SpringBootApplication。

  1、SpringBootApplication的结构

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

   可以看到其上面的Spring自定义注解有@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。这里的@ComponentScan就是用来扫描这个SpringbootdemoApplication 所在的包及其下面的子包,这个就不过多赘叙了。

  1、@SpringBootConfiguration

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

   可以看到其上面就是加了一个@Configuration,用于给ConfigurationClassPostProcessor来扫描处理。

 2、@EnableAutoConfiguration

   这个@EnableAutoConfiguration就是@SpringBootApplication的主要引入内容了,用来加载在spring-boot-autoconfigure模块中定义的那些自动配置的类了。

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

   可以看到其通过@Import引入了一个ImportSelector(AutoConfigurationImportSelector),这个就是整个引入的关键。

public class AutoConfigurationImportSelector
      implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
      BeanFactoryAware, EnvironmentAware, Ordered {
    .............
   @Override
   public String[] selectImports(AnnotationMetadata annotationMetadata) {
      if (!isEnabled(annotationMetadata)) {
         return NO_IMPORTS;
      }
      AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
      AnnotationAttributes attributes = getAttributes(annotationMetadata);
      List<String> configurations = getCandidateConfigurations(annotationMetadata,
            attributes);
      configurations = removeDuplicates(configurations);
      Set<String> exclusions = getExclusions(annotationMetadata, attributes);
      checkExcludedClasses(configurations, exclusions);
      configurations.removeAll(exclusions);
      configurations = filter(configurations, autoConfigurationMetadata);
      fireAutoConfigurationImportEvents(configurations, exclusions);
      return StringUtils.toStringArray(configurations);
   }

   在分析这个AutoConfigurationImportSelector之前我们首先需要了解两个文件。

   1、两个properties文件

protected static final String PATH = "META-INF/"
      + "spring-autoconfigure-metadata.properties";
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

  首先我们通过这两个文件的内容来看下它们之间的联系,我们通过在前一篇文章注入JdbcTemplate的类JdbcTemplateAutoConfiguration 来分析分析。

@Configuration
@ConditionalOnClass({DataSource.class, JdbcTemplate.class})
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(JdbcProperties.class)
public class JdbcTemplateAutoConfiguration {

   我们知道其有注解@ConditionalOnClass,所以需要有DataSource.class, JdbcTemplate.class才会去处理这个类。

  1、spring.factories

...............
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
................
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
...........

  我们看到其其实通过key-value[]的形式设置的。这个可以key就是我们在前面提到的@EnableAutoConfiguration注解,value[]就是我们用来注入JdbcTemplate、或者DataSource所需要的的JdbcTemplateAutoConfiguration、DataSourceAutoConfiguration。

  2、spring-autoconfigure-metadata.properties

...........
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.core.JdbcTemplate
.........

    可以看到这个key是"JdbcTemplateAutoConfiguration"+".ConditionalOnClass",而value就是@ConditionalOnClass所需要的类。

  2、AutoConfigurationImportSelector

   下面我们再来分析AutoConfigurationImportSelector的selectImports方法。

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
          return NO_IMPORTS;
   }
    ..............
   AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
         .loadMetadata(this.beanClassLoader);

   AnnotationAttributes attributes = getAttributes(annotationMetadata);

   List<String> configurations = getCandidateConfigurations(annotationMetadata,
         attributes);
   configurations = removeDuplicates(configurations);
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);

   configurations = filter(configurations, autoConfigurationMetadata);

   fireAutoConfigurationImportEvents(configurations, exclusions);

   return StringUtils.toStringArray(configurations);
}

    1、isEnabled方法

protected boolean isEnabled(AnnotationMetadata metadata) {
   if (getClass() == AutoConfigurationImportSelector.class) {
      return getEnvironment().getProperty(
            EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
            true);
   }
   return true;
}
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

   这个入参就是描叙对应的Class信息的,例如有哪些注解,首先是从环境中看需不需要默认注入配置。

   2、AutoConfigurationMetadataLoader .loadMetadata

protected static final String PATH = "META-INF/"
      + "spring-autoconfigure-metadata.properties";
........
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
   return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
   try {
      Enumeration<URL> 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);
     ........
}

   可以看到这里就是加载spring-autoconfigure-metadata.properties文件中的内容。

  3、getAttributes

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......);
   return attributes;
}
protected Class<?> getAnnotationClass() {
   return EnableAutoConfiguration.class;
}

   这个就是用来获取该Class上面@EnableAutoConfiguration注解里面的属性配置的

                   SpringBoot 源码解读系列(3)[email protected]注解的源码处理分析

  4、getCandidateConfigurations

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
                                      AnnotationAttributes attributes) {
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
         getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
   .........
   return configurations;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
   return EnableAutoConfiguration.class;
}

     这个SpringFactoriesLoader.loadFactoryNames就是用来加载spring.factories的内容的。这里传入的getSpringFactoriesLoaderFactoryClass(),就是EnableAutoConfiguration,所以这里就会获取spring.factories中key为EnableAutoConfiguration(会带包名)的,例如JdbcTemplateAutoConfiguration、DataSourceAutoConfiguration。

5、filter

  上面的那些removeAll、exclusions就不再分析了,就是删除一些需要排除的。

private List<String> filter(List<String> configurations,
                     AutoConfigurationMetadata autoConfigurationMetadata) {
   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;
            skipped = true;
         }
      }
   }
  ........
   List<String> result = new ArrayList<>(candidates.length);
   for (int i = 0; i < candidates.length; i++) {
      if (!skip[i]) {
         result.add(candidates[i]);
      }
   }
    ...........
   return new ArrayList<>(result);
}

   这里就偶是用来通过AutoConfigurationImportFilter去match,然后通过

if (!skip[i]) {
   result.add(candidates[i]);
}

  如果匹配,就添加到result中,result的内容就是spring.factories获取的XXXConfiguration最终需要注入到Bean容器的内容。那这个filter.match是主要匹配的呢?

    我们首先需要看下通过getAutoConfigurationImportFilters方法有获取哪些AutoConfigurationImportFilter

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

   可以看到这里会去spring.ffactories文件中获取key为AutoConfigurationImportFilter的value

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition

  然后获取到的就是OnClassCondition。这个OnClassCondition在上一篇有提到,其实一个Condition。

@Override
public boolean[] match(String[] autoConfigurationClasses,
                  AutoConfigurationMetadata autoConfigurationMetadata) {
   ConditionEvaluationReport report = getConditionEvaluationReport();
   ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses,
         autoConfigurationMetadata);
   boolean[] match = new boolean[outcomes.length];
   ..........
   return match;
}

      这里首先需要注意的一个点就是autoConfigurationClasses中放到是spring.factories文件获取的value[],autoConfigurationMetadata中获取的是从spring-autoconfigure-metadata.properties文件获取的内容。

     我们直接来到其主要的逻辑处理

private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
                              int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
   ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
   for (int i = start; i < end; i++) {
      String autoConfigurationClass = autoConfigurationClasses[i];
      Set<String> candidates = autoConfigurationMetadata
            .getSet(autoConfigurationClass, "ConditionalOnClass");
      if (candidates != null) {
         outcomes[i - start] = getOutcome(candidates);
      }
   }
   return outcomes;
}

   这里就是以autoConfigurationClasses[i]为key再拼接"ConditionalOnClass",例如我们前面在将这两个文件的时候:

例如"autoConfigurationClass "为"org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration",这里拼接的就是"org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.ConditionalOnClass",然后以这个为key从spring-autoconfigure-metadata.properties文件中获取:

org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.core.JdbcTemplate

   这里就获取到了JdbcTemplate、DataSource。然后再调用getOutcome方法(前面调用的是getOutcomes)

private ConditionOutcome getOutcome(Set<String> candidates) {
   try {
      List<String> missing = getMatches(candidates, MatchType.MISSING,
            this.beanClassLoader);
      if (!missing.isEmpty()) {
         return ConditionOutcome.noMatch(
               .........
      }
   }
   catch (Exception ex) {
      // We'll get another chance later
   }
   return null;
}
MISSING {
   @Override
   public boolean matches(String className, ClassLoader classLoader) {
      return !isPresent(className, classLoader);
   }
};
private static boolean isPresent(String className, ClassLoader classLoader) {
   if (classLoader == null) {
      classLoader = ClassUtils.getDefaultClassLoader();
   }
   try {
      forName(className, classLoader);
      return true;
   } catch (Throwable ex) {
      return false;
   }
}

     所以这里其主要是一个提取校验并且记录校验的结果,看需要注入的例如JdbcTemplateAutoConfiguration,满不满足,@ConditionalOnClass所需的。如果校验通过就返回通过,然后就会将其添加到result中。之后再注入到Bean容器中。

   以上就是关于@SpringBootApplication注解的大体分析。