spring boot 的 auto-configuration 功能会根据你的应用程序所依赖的 pom 来进行自动配置。 例如,我们在 pom 中添加 spring-boot-starter-web的依赖,spring 就会帮我们自动完成 spring mvc 相关的配置而不需要我们手动来进行。我们只需要将 @EnableAutoConfiguration 或者 @SpringBootApplication注解标注在 @Configuration 配置类上面即可启用自动装配。

那么 auto-configuration 是如何工作的呢?带着问题,我们通过阅读相关的源代码来一探究竟。

既然开启 auto-configuration 需要通过 @EnableAutoConfiguration 或 @SpringBootApplication来驱动,那么我们就从这两个注解着手。

注:本文基于 spring boot 版本 1.5.12.RELEASE。

源码解析

• @SpringBootApplication

源码如下:

1. @Target(ElementType.TYPE)
2. @Retention(RetentionPolicy.RUNTIME)
3. @Documented
4. @Inherited
5. @SpringBootConfiguration
6. @EnableAutoConfiguration
7. @ComponentScan(excludeFilters = {
8.         @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
9.         @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
10. public @interface SpringBootApplication {
11.     // 省略
12. }
13.

  1. @SpringBootApplication注解本身也标注了 @EnableAutoConfiguration 注解,所以自动装配最终还是通过 @EnableAutoConfiguration来启用。
     
  2.  
    @SpringBootApplication还标注了 @ComponentScan和 @SpringBootConfiguration, @SpringBootConfiguration上又标注 @Configuration注解。
     

因为 @SpringBootApplication将上述多个注解集成于一身,所以我们只要在类上标注 @SpringBootApplication就等价于同时添加了 @EnableAutoConfiguration、 @ComponentScan和 @Configuration


注:类上标注的注解(direct annotation)和注解上的注解(meta-annotation)之所以都能生效这和 spring 本身对注解的处理有关。

@SpringBootApplication 比较简单,看来再来看看 @EnableAutoConfiguration

  • @EnableAutoConfiguration

源码如下:

1. @SuppressWarnings("deprecation")
2. @Target(ElementType.TYPE)
3. @Retention(RetentionPolicy.RUNTIME)
4. @Documented
5. @Inherited
6. @AutoConfigurationPackage
7. @Import(EnableAutoConfigurationImportSelector.class)
8. public @interface EnableAutoConfiguration {
9.  
10.     String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
11.  
12.     /**
13.      * Exclude specific auto-configuration classes such that they will never be applied.
14.      * @return the classes to exclude
15.      */
16.     Class<?>[] exclude() default {};
17.  
18.     /**
19.      * Exclude specific auto-configuration class names such that they will never be
20.      * applied.
21.      * @return the class names to exclude
22.      * @since 1.3.0
23.      */
24.     String[] excludeName() default {};
25.  
26. }

@EnableAutoConfiguration 上标注了 @Import,引入了 EnableAutoConfigurationImportSelector

EnableAutoConfigurationImportSelector 源码如下:

1. public class EnableAutoConfigurationImportSelector
2.         extends AutoConfigurationImportSelector {
3.  
4.     @Override
5.     protected boolean isEnabled(AnnotationMetadata metadata) {
6.         if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
7.             return getEnvironment().getProperty(
8.                     EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
9.                     true);
10.         }
11.         return true;
12.     }
13.  
14. }

EnableAutoConfigurationImportSelector 源码并没能给我们提供太多参考信息,只重写了 isEnabled方法,我们来看看他的基类 AutoConfigurationImportSelector,源码如下:

1. public class AutoConfigurationImportSelector
2.         implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
3. BeanFactoryAware, EnvironmentAware, Ordered {
4.     // 省略
5. }

我们看到 AutoConfigurationImportSelector实现了 DeferredImportSelector接口(从 ImportSelector派生)。 ImportSelector源码如下:

1. public interface ImportSelector {
2.  
3.     /**
4.      * Select and return the names of which class(es) should be imported based on
5.      * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
6.      */
7.     String[] selectImports(AnnotationMetadata importingClassMetadata);
8.  
9. }

selectImports方法中定义了参数 importingClassMetadata ,类型是 AnnotationMetadata ,这是啥?

我们把 importingClassMetadata这个词组分解一下,importing + class metadata:

importing:动名词,强调的是干了 import 这件事的发起者(这里指配置类)

class metadata:类的元信息,什么元信息,注解元信息,即 AnnotationMetadata

那么 selectImports方法的逻辑,我们可以这么描述:

基于发起 import 操作的配置类(@Configuration class)的元信息进行运算并返回计算结果(class 名称数组)。

那么 spring 对返回结果中的 class 有没有什么特别要求呢?

实际上你可以返回任意的 class 名称,而不至于使程序出错,当然,你肯定不会返回没有任何意义的 class 名称。笔者总结的返回结果分为下面两类:

  • @Configuration 配置类
  • ImportSelector(或 DeferredImportSelector)实现类

ImportSelector与 DeferredImportSelector的区别?

DeferredImportSelector是 ImportSelector的变体,二者的触发先后顺序不同, DeferredImportSelector在所有的 @Configurationbean 都被处理了之后再进行处理,这与它的名称 deferred (推迟)非常贴合。

题外话:通过以上内容,我们可以看出 spring 在命名上非常讲究,代码阅读起来比较符合人的逻辑思维。

selectImports 方法的实现由 ConfigurationClassParser 类的 parse 方法触发, ConfigurationClassParser的 parse 方法会被 ConfigurationClassPostProcessor类的 postProcessBeanDefinitionRegistry 方法调用,而触发这一切的最上层是 spring application context 的 refresh()方法。

信息量有点大,我们通过下图来做简单说明:

1. Application 启动
2.     ⥥ // refresh spring application context
3. AbstractApplicationContext.refresh
4.     ⥥ // 执行 BeanFactoryPostProcessor 钩子回调
5. AbstractApplicationContext.invokeBeanFactoryPostProcessors
6.     ⥥ // 委托给 PostProcessorRegistrationDelegate 来执行钩子回调
7. PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors
8.     ⥥ // ConfigurationClassPostProcessor 是 BeanFactoryPostProcessor 的实现类,被触发
9. ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry
10.     ⥥ // ConfigurationClassPostProcessor 委托 ConfigurationClassParser 来对配置类进行解析
11. ConfigurationClassParser.parse
12.     ⥥ // ConfigurationClassParser 处理 DeferredImportSelector 
13. ConfigurationClassParser.processDeferredImportSelectors
14.     ⥥ // 执行 selectImports
15. DeferredImportSelector.selectImports

 

关于 ConfigurationClassParser类对 @Configuration配置类的处理,本文并不打算作过多讲解,笔者会放在后续文章中来和大家进行探讨。

好了,有了对 ImportSelector的相关了解后,我们来看看 AutoConfigurationImportSelector的 selectImports方法的实现,源码如下:

1. @Override
2.     public String[] selectImports(AnnotationMetadata annotationMetadata) {
3.         // 检查是否启用 auto-configuration,默认为启用 
4.         // 由子类 EnableAutoConfigurationImportSelector 重写
5.         if (!isEnabled(annotationMetadata)) {
6.             // 如果不开启 auto-configuration,返回 emtpy array
7.             return NO_IMPORTS;
8.         }
9.         try {
10.             AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
11.                     .loadMetadata(this.beanClassLoader);
12.             AnnotationAttributes attributes = getAttributes(annotationMetadata);
13.             // 通过 SpringFactoriesLoader 找到所有的侯选配置类(auto-configuration 类)
14.             List<String> configurations = getCandidateConfigurations(annotationMetadata,
15.                     attributes);
16.             // 去除重复的配置类 
17.             configurations = removeDuplicates(configurations);
18.             // 根据 auto-configuration 的先后配置顺序的要求进行排序
19.             // 可通过 @AutoConfigureBefore & @AutoConfigureAfter 来指定
20.             configurations = sort(configurations, autoConfigurationMetadata);
21.             // 需要被剔除的 auto-configuration 类
22.             // 可在 properties 配置文件中通过 spring.autoconfigure.exclude 来指定
23.             // 也可通过 @EnableAutoConfiguration 注解的 exclude() & excludeName() 属性来指定
24.             Set<String> exclusions = getExclusions(annotationMetadata, attributes);
25.             checkExcludedClasses(configurations, exclusions);
26.             // 从侯选配置类中剔除需要被排除在外的(auto-configuration 类)
27.             configurations.removeAll(exclusions);
28.             // 通过 AutoConfigurationImportFilter 来过滤侯选配置类,再次进行剔除
29.             // 通过 SpringFactoriesLoader 获取所的 AutoConfigurationImportFilter 实现
30.             configurations = filter(configurations, autoConfigurationMetadata);
31.             // 发布 AutoConfigurationImportEvent 事件,通知 AutoConfigurationImportListener,
32.             // 触发 onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) 方法
33.             // 通过 SpringFactoriesLoader 获取所的 AutoConfigurationImportListener 实现
34.             fireAutoConfigurationImportEvents(configurations, exclusions);
35.             // 返回需要处理的 auto-configuration 类(名称)
36.             return configurations.toArray(new String[configurations.size()]);
37.         }
38.         catch (IOException ex) {
39.             throw new IllegalStateException(ex);
40.         }
41.     }
42.  
43.     protected boolean isEnabled(AnnotationMetadata metadata) {
44.         return true;
45.     }

相信大家看了笔者所添加的相关注释后对 AutoConfigurationImportSelector的逻辑已经有了一个大致的了解。

AutoConfigurationImportSelector的 selectImports方法的主要逻辑就是通过 SpringFactoriesLoader找到所有的 auto-configuration 侯选类,然后在此基础上进行去重、排序和剔除操作,最终得到需要进行 auto-configuration 的所有类的名称。

拿到了所的 auto-configuration 类,spring boot 就可以加载这些 class,由于这些类本身标注了 @Configuration,然后就可以被 ConfigurationClassParser类来进行解析了,最终 @Bean工厂方法就会被调用,完成 bean 的加载。

在笔者的注释中,不止一次提到了 SpringFactoriesLoader,这个类究竟有何神奇之处?

同样的,为了搞清楚这件事,我们还得看 spring 源码,部分源码如下:

1. public abstract class SpringFactoriesLoader {
2.  
3.     /**
4.      * The location to look for factories.
5.      * <p>Can be present in multiple JAR files.
6.      */
7.     public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
8.  
9.     // 通过 ClassLoader 加载所有的所有 META-INF/spring.factories 文件,解析成 Properties 对象,
10.     // 根据 key (指定的 class 名称) 来获取所有的配置项 (类名称)
11.     public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
12.         String factoryClassName = factoryClass.getName();
13.         try {
14.             Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
15.                     ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
16.             List<String> result = new ArrayList<String>();
17.             while (urls.hasMoreElements()) {
18.                 URL url = urls.nextElement();
19.                 Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
20.                 String factoryClassNames = properties.getProperty(factoryClassName);
21.                 result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
22.             }
23.             return result;
24.         }
25.         catch (IOException ex) {
26.             // 省略
27.         }
28.     }
29. }

原来 spring 使用了一种类似 ServiceLoader (jdk 1.6 中增加)的处理方式。通过这种约定,我们只需要将要进行处理的目标类名称配置在相对固定的配置文件中,spring 按照统一的方式来读取即可。对我们的应用程序而言并不需要知道具体的实现类是哪些,也不需要知道这些类存在于哪些 jar 中,都可以被轻松获取。

相对固定指的是:

  •  
    相对于 classpath, META-INF/spring.factories 文件的位置固定和名称固定。
     
  •  
    同一类别的配置,不同配置文件中 key 名称固定不变。
    注:如果你对 ServiceLoader感到陌生,请查看 jdk api 了解相关内容。
     

既然了解了 spring 的 spring.factories 的套路,那么我们就来找找看都有哪些 auto-configuration 类,

我们的 key 是 @EnableAutoConfiguration注解的 class 名称,即 org.springframework.boot.autoconfigure.EnableAutoConfiguration,展开 spring-boot-autoconfigure-1.5.12.RELEASE.jar 文件,找到 spring.factories 文件,部分配置如下(“……” 表示笔者省略 ):

1. # Auto Configure
2. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
3. org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
4. org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
5. org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
6. org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
7. org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
8. # …… 
9. org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
10. org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
11. org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
12. # ……
13. org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
14. # ……
15. org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
16. org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
17. # ……
18. org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
19. org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
20. org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
21. org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
22. org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
23. org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
24. org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

上述这些 auto-configuration 侯选类名称都会被 AutoConfigurationImportSelector通过 SpringFactoriesLoader所获取,然后 AutoConfigurationImportSelector就可以进行后续的去重、排序和剔除过滤操作了。

好了,本文至此,你是否已经对 spring boot 的 auto-configuration 工作机制有所了解了呢?

小结

本文,我们主要介绍了 spring boot auto-configuration 的工作机制,提到了几个重要的概念:

• @EnableAutoConfiguration
• ImportSelector
• SpringFactoriesLoader

通过 @EnableAutoConfiguration注解驱动引入 AutoConfigurationImportSelector,该类通过 SpringFactoriesLoader加载所有的 META-INF/spring.factories文件,获取到所有的 auto-configuration 候选类,然后进行去重、排序和剔除过滤等操作得到待处理的 auto-configuration 类。