原文链接

SpringBoot 的自动化配置让我们的开发彻底远离了 Spring 繁琐的各种配置,让我们专注于开发,但是SpringBoot 的自动化配置是怎么实现的呢?下面为你揭开 SpringBoot 自动化配置的神秘面纱。
SpringBoot 最为重要的一个注解就是 @SpringBootApplication,它其实是一个组合元注解:

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @SpringBootConfiguration
  6. @EnableAutoConfiguration
  7. @ComponentScan(
  8. excludeFilters = {@Filter(
  9. type = FilterType.CUSTOM,
  10. classes = {TypeExcludeFilter.class}
  11. ), @Filter(
  12. type = FilterType.CUSTOM,
  13. classes = {AutoConfigurationExcludeFilter.class}
  14. )}
  15. )
  16. public @interface SpringBootApplication {
  17. @AliasFor(
  18. annotation = EnableAutoConfiguration.class,
  19. attribute = "exclude"
  20. )
  21. // 此处省略部分代码
  22. }

从这个注解可看出,它包含了 @EnableAutoConfiguration 这个注解,这个注解就是 SpringBoot 自动化配置原理的核心所在:

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @AutoConfigurationPackage
  6. @Import({EnableAutoConfigurationImportSelector.class})
  7. public @interface EnableAutoConfiguration {
  8. String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  9. Class<?>[] exclude() default {};
  10. String[] excludeName() default {};
  11. }

我们发现它使用了 Spring 框架提供的 @Import 注解注入了注册 Bean 的配置类,在往下分析前,不妨先了解一下这个 @Import 注解,在我们平时使用 Spring 框架的 Enable* 类注解时,发现它们都有一个共同的特点,就是都有一个 @Import 注解,用来导入配置类,这些配置方式又分为三种类型:

  1. 直接导入配置类:@Import({xxxConfiguration.class})
  2. 依据条件选择配置类:@Import({xxxSelector.class})
  3. 动态注册 Bean:@Import({xxxRegistrar.class})

很明显,@EnableAutoConfiguration 这个注解使用的是第三种情况,导入 EnableAutoConfigurationImportSelector 类,继续跟踪源码:

  1. public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector {
  2. public EnableAutoConfigurationImportSelector() {
  3. }
  4. protected boolean isEnabled(AnnotationMetadata metadata) {
  5. return this.getClass().equals(EnableAutoConfigurationImportSelector.class) ? ((Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true)).booleanValue() : true;
  6. }
  7. }

查看父类源码:

  1. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  2. if (!this.isEnabled(annotationMetadata)) {
  3. return NO_IMPORTS;
  4. } else {
  5. try {
  6. // 此处省略部分代码
  7. List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
  8. // 此处省略部分代码
  9. return (String[])configurations.toArray(new String[configurations.size()]);
  10. } catch (IOException var6) {
  11. throw new IllegalStateException(var6);
  12. }
  13. }
  14. }
  15. protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  16. List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
  17. 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.");
  18. return configurations;
  19. }

我们最终发现它其实是实现了 ImportSelector 接口:

  1. public interface ImportSelector {
  2. String[] selectImports(AnnotationMetadata var1);
  3. }

实现 ImportSelectors 接口的类通常与常规的 @Import 注解作用相同,它 的 selectImports() 方法返回的数组(类的全类名)都会被纳入到 Spring 容器中。
到这里,自动化配置幕后英雄终于出现了,它就是 Spring 的 SpringFactoriesLoader 类,该类专门用于加载 classpath下所有 JAR 文件的 META-INF/spring.factories 文件,不妨看看它的源码:

  1. public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
  2. String factoryClassName = factoryClass.getName();
  3. try {
  4. // 加载 spring.factories 中配置类的url
  5. Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
  6. ArrayList result = new ArrayList();
  7. // 循环读取每个配置类路径
  8. while(urls.hasMoreElements()) {
  9. URL url = (URL)urls.nextElement();
  10. Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
  11. String factoryClassNames = properties.getProperty(factoryClassName);
  12. result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
  13. }
  14. return result;
  15. } catch (IOException var8) {
  16. throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
  17. }
  18. }

我们去看看 spring.factories 到底长什么样子:
image.png
spring.factories

此处省略部分配置

  1. # Initializers
  2. org.springframework.context.ApplicationContextInitializer=\
  3. org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
  4. org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
  5. # 此处省略部分配置
  6. # Auto Configure
  7. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  8. org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
  9. org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
  10. org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
  11. org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\

柳暗花明又一村,我们最终得出 SpringBoot 自动化配置要干的事情就是在启动过程中将 spring.factories 中相关的自动化配置类进行解析。
接下来我们就来分析自动化配置类:
Redis 官方的 RedisAutoConfiguration 配置类:

  1. @Configuration
  2. @ConditionalOnClass({JedisConnection.class, RedisOperations.class, Jedis.class})
  3. @EnableConfigurationProperties({RedisProperties.class})
  4. public class RedisAutoConfiguration {
  5. public RedisAutoConfiguration() {
  6. }
  7. @Configuration
  8. protected static class RedisConfiguration {
  9. protected RedisConfiguration() {
  10. }
  11. @Bean
  12. @ConditionalOnMissingBean(
  13. name = {"redisTemplate"}
  14. )
  15. public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
  16. RedisTemplate<Object, Object> template = new RedisTemplate();
  17. template.setConnectionFactory(redisConnectionFactory);
  18. return template;
  19. }
  20. // 此处省略部分代码
  21. }
  22. }

我们看到了 @ConditionalOnClass 和 @ConditionalOnMissingBean 这些注解,它们都是 SpringBoot的条件注解:
image.png
conditional
想要知道这些注解有什么功能,这里就不展开讲了,可以去查阅 SpringBoot 官方文档。以下主要是分析这些注解是如何进行工作的。

  1. @ConditionalOnClass
  2. @Target({ElementType.TYPE, ElementType.METHOD})
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Documented
  5. @Conditional({OnClassCondition.class})
  6. public @interface ConditionalOnClass {
  7. Class<?>[] value() default {};
  8. String[] name() default {};
  9. }

可以看出,这些这些条件注解都组合了 @Conditional 元注解,只是使用了不同的条件,继续往下看 OnClassCondition 条件是如何工作的:

  1. @Order()
  2. class OnClassCondition extends SpringBootCondition implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
  3. private BeanFactory beanFactory;
  4. private ClassLoader beanClassLoader;
  5. OnClassCondition() {
  6. }
  7. public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
  8. // 此处省略部分代码
  9. }
  10. // 此处省略部分代码
  11. }

SpringBootCondition 实现了 Spring 的 Condition 接口,也就是并重写其 matche() 方法来构造判断条件。Condition 可以用于判断 Configuration 配置类需要满足什么条件才可以装进 Spring 容器。
当我们需要在 application.properties 中加入自定义的配置,那么 SpringBoot 是如何根据 application.properties 来实现自定义配置呢?我们往回看,发现了 @EnableConfigurationProperties({RedisProperties.class}) 这个注解,这个注解就是用来读取 application.properties 中对应的配置信息对应到 POJO 类当中:
RedisProperties.java

  1. @ConfigurationProperties(
  2. prefix = "spring.redis"
  3. )
  4. public class RedisProperties {
  5. private int database = 0;
  6. private String url;
  7. private String host = "localhost";
  8. private String password;
  9. private int port = 6379;
  10. private boolean ssl;
  11. private int timeout;
  12. private RedisProperties.Pool pool;
  13. private RedisProperties.Sentinel sentinel;
  14. private RedisProperties.Cluster cluster;
  15. // 此处省略getter和setter
  16. }