springboot 自动化配置源码分析

 

SpringBoot 的自动化配置让我们的开发彻底远离了 Spring 繁琐的各种配置,让我们专注于开发,但是SpringBoot 的自动化配置是怎么实现的呢?下面为你揭开 SpringBoot 自动化配置的神秘面纱。

SpringBoot 最为重要的一个注解就是 @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 {
   /**
    * Exclude specific auto-configuration classes such that they will never be applied.
    * @return the classes to exclude
    */
   @AliasFor(annotation = EnableAutoConfiguration.class)
   Class<?>[] exclude() default {};
   /**
    * Exclude specific auto-configuration class names such that they will never be
    * applied.
    * @return the class names to exclude
    * @since 1.3.0
    */
   @AliasFor(annotation = EnableAutoConfiguration.class)
   String[] excludeName() default {};
   /**
    * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
    * for a type-safe alternative to String-based package names.
    * @return base packages to scan
    * @since 1.3.0
    */
   @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
   String[] scanBasePackages() default {};
   /**
    * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
    * scan for annotated components. The package of each class specified will be scanned.
    * <p>
    * Consider creating a special no-op marker class or interface in each package that
    * serves no purpose other than being referenced by this attribute.
    * @return base packages to scan
    * @since 1.3.0
    */
   @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
   Class<?>[] scanBasePackageClasses() default {};
}

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
   /**
    * Exclude specific auto-configuration classes such that they will never be applied.
    * @return the classes to exclude
    */
   Class<?>[] exclude() default {};
   /**
    * Exclude specific auto-configuration class names such that they will never be
    * applied.
    * @return the class names to exclude
    * @since 1.3.0
    */
   String[] excludeName() default {};
}

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

直接导入配置类:@Import({xxxConfiguration.class})

依据条件选择配置类:@Import({xxxSelector.class})

动态注册 Bean:@Import({xxxRegistrar.class})

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
   /**
    * Exclude specific auto-configuration classes such that they will never be applied.
    * @return the classes to exclude
    */
   Class<?>[] exclude() default {};
   /**
    * Exclude specific auto-configuration class names such that they will never be
    * applied.
    * @return the class names to exclude
    * @since 1.3.0
    */
   String[] excludeName() default {};
}

 

查看源码:

public class AutoConfigurationImportSelector
      implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
      BeanFactoryAware, EnvironmentAware, Ordered {
   private static final String[] NO_IMPORTS = {};
   private static final Log logger = LogFactory
         .getLog(AutoConfigurationImportSelector.class);
   private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
   private ConfigurableListableBeanFactory beanFactory;
   private Environment environment;
   private ClassLoader beanClassLoader;
   private ResourceLoader resourceLoader;
   @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);
   }
   @Override
   public Class<? extends Group> getImportGroup() {
      return AutoConfigurationGroup.class;
   }
   protected boolean isEnabled(AnnotationMetadata metadata) {
      if (getClass() == AutoConfigurationImportSelector.class) {
         return getEnvironment().getProperty(
               EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
               true);
      }
      return true;
   }
//         此处省略部分代码。。。。。。。。。。。。。。
   /**
    * Return the auto-configuration class names that should be considered. By default
    * this method will load candidates using {@link SpringFactoriesLoader} with
    * {@link #getSpringFactoriesLoaderFactoryClass()}.
    * @param metadata the source metadata
    * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
    * attributes}
    * @return a list of candidate configurations
    */
   protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
         AnnotationAttributes attributes) {
      List<String> 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;
   }

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

public interface ImportSelector {
   /**
    * Select and return the names of which class(es) should be imported based on
    * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
    */
   String[] selectImports(AnnotationMetadata importingClassMetadata);
}

实现 ImportSelectors 接口的类通常与常规的 @Import 注解作用相同,它 的 selectImports() 方法返回的数组(类的全类名)都会被纳入到 Spring 容器中。

到这里,自动化配置幕后英雄终于出现了,它就是 Spring 的 SpringFactoriesLoader 类,该类专门用于加载 classpath下所有 JAR 文件的 META-INF/spring.factories 文件,不妨看看它的源码:

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
   String factoryClassName = factoryClass.getName();
   return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
   MultiValueMap<String, String> result = cache.get(classLoader);
   if (result != null) {
      return result;
   }
   try {
      Enumeration<URL> 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()) {
            List<String> factoryClassNames = Arrays.asList(
                  StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
            result.addAll((String) entry.getKey(), factoryClassNames);
         }
      }
      cache.put(classLoader, result);
      return result;
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
}

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

spring.factories

# Initializersorg.springframework.context.ApplicationContextInitializer=\org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer# 此处省略部分配置# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\# 此处省略部分配置
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

# 此处省略部分配置
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
# 此处省略部分配置

 

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

接下来我们就来分析自动化配置类:

Redis 官方的 RedisAutoConfiguration 配置类:

@Configuration@ConditionalOnClass({JedisConnection.class, RedisOperations.class, Jedis.class})@EnableConfigurationProperties({RedisProperties.class})public class RedisAutoConfiguration {  public RedisAutoConfiguration() {  }  @Configuration  protected static class RedisConfiguration {    protected RedisConfiguration() {    }    @Bean    @ConditionalOnMissingBean(      name = {"redisTemplate"}    )    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {      RedisTemplate<Object, Object> template = new RedisTemplate();      template.setConnectionFactory(redisConnectionFactory);      return template;    }    // 此处省略部分代码  }}
@ConditionalOnClass({JedisConnection.class, RedisOperations.class, Jedis.class})
@EnableConfigurationProperties({RedisProperties.class})
public class RedisAutoConfiguration {
  public RedisAutoConfiguration() {
  }

  @Configuration
  protected static class RedisConfiguration {
    protected RedisConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
      name = {"redisTemplate"}
    )
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
      RedisTemplate<Object, Object> template = new RedisTemplate();
      template.setConnectionFactory(redisConnectionFactory);
      return template;
    }

    // 此处省略部分代码

  }
}

 

我们看到了 @ConditionalOnClass 和 @ConditionalOnMissingBean 这些注解,它们都是 SpringBoot的条件注解:

conditional

想要知道这些注解有什么功能,这里就不展开讲了,可以去查阅 SpringBoot 官方文档。以下主要是分析这些注解是如何进行工作的。

@ConditionalOnClass

@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Conditional({OnClassCondition.class})public @interface ConditionalOnClass {  Class<?>[] value() default {};  String[] name() default {};}
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
  Class<?>[] value() default {};
  String[] name() default {};
}

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

 

@Order()class OnClassCondition extends SpringBootCondition implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {  private BeanFactory beanFactory;  private ClassLoader beanClassLoader;  OnClassCondition() {  }  public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {     // 此处省略部分代码  }  // 此处省略部分代码}
class OnClassCondition extends SpringBootCondition implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
  private BeanFactory beanFactory;
  private ClassLoader beanClassLoader;

  OnClassCondition() {
  }

  public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
     // 此处省略部分代码
  }
  // 此处省略部分代码
}

 

SpringBootCondition 实现了 Spring 的 Condition 接口,也就是并重写其 matche() 方法来构造判断条件。Condition 可以用于判断 Configuration 配置类需要满足什么条件才可以装进 Spring 容器。

当我们需要在 application.properties 中加入自定义的配置,那么 SpringBoot 是如何根据  application.properties 来实现自定义配置呢?我们往回看,发现了 @EnableConfigurationProperties({RedisProperties.class}) 这个注解,这个注解就是用来读取 application.properties 中对应的配置信息对应到 POJO 类当中:

RedisProperties.java

@ConfigurationProperties(    prefix = "spring.redis")public class RedisProperties {    private int database = 0;    private String url;    private String host = "localhost";    private String password;    private int port = 6379;    private boolean ssl;    private int timeout;    private RedisProperties.Pool pool;    private RedisProperties.Sentinel sentinel;    private RedisProperties.Cluster cluster;    // 此处省略getter和setter}
    prefix = "spring.redis"
)
public class RedisProperties {
    private int database = 0;
    private String url;
    private String host = "localhost";
    private String password;
    private int port = 6379;
    private boolean ssl;
    private int timeout;
    private RedisProperties.Pool pool;
    private RedisProperties.Sentinel sentinel;
    private RedisProperties.Cluster cluster;
    // 此处省略getter和setter
}

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值