SpringBoot自动配置原理分析

138 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第14天,点击查看活动详情

在SpringBoot中约定大于配置。我们通过引入不同的官方或者第三方的场景启动器,就能应用不同场景的开发。只要想修改就直接通过配置文件定义就行,这样的自动配置原理又是什么呢?我们来一起看看吧~

引导加载自动配置类

我们从主启动程序入手分析。

image-20201225152004423

@SpringBootApplication
public class SpringbootApplication {

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

}

这里标注了一个@SpringBootApplication注解,我们点击查看源码。

image-20201225152316996

这个注解除了元注解,标注了三个其他的注解。

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

@SpringBootConfiguration

我们首先来分析一下@SpringBootConfiguration注解,查看源码

image-20201225152516443

@Configuration//标志当前是一个配置类
public @interface SpringBootConfiguration {
   @AliasFor(annotation = Configuration.class)
   boolean proxyBeanMethods() default true;

}

这个类除了元注解就只有@Configuration,所以它的作用也就是标志当前是一个配置类。也就是说我们的主启动程序,也是一个配置类,不过是SpringBoot的一个核心配置类。

@ComponentScan

//SpringBoot配置类
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

接着我们在来看一下@ComponentScan:主要就是定义扫描的路径从中找出标识了需要装配的类自动装配到spring的bean容器中。

不过这里指定了那些类型和类不扫描,也就是过滤掉。

//SpringBoot配置类
@SpringBootConfiguration
@EnableAutoConfiguration
//组件扫描自动装配到IOC容器
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

@EnableAutoConfiguration

接下来只剩下@EnableAutoConfiguration注解了。这个就是自动配置的核心注解。我们查看源码

image-20201225153752376

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";


   Class<?>[] exclude() default {};


   String[] excludeName() default {};

}

发现有两个注解@AutoConfigurationPackage@Import

@AutoConfigurationPackage

查看@AutoConfigurationPackage源码。

image-20201225154015566

//导入AutoConfigurationPackages.Registrar对象
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

   String[] basePackages() default {};

   Class<?>[] basePackageClasses() default {};

}

发现该类通过@Import导入了AutoConfigurationPackages.Registrar组件。我们继续点进去查看源码

image-20201225154237417

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
   }

   @Override
   public Set<Object> determineImports(AnnotationMetadata metadata) {
      return Collections.singleton(new PackageImports(metadata));
   }

}

发现该类只有两个方法registerBeanDefinitions,determineImports.我们在第一个方法中打一个断点,debug查看。

image-20201225154808593

AnnotationMetadata的信息,来自标注@SpringBootApplication(合成注解)的主启动类上。

image-20201225155129503

接着通过AnnotationMetadata或者注解标注类所在的包信息转换成一个数组。然后register注册到IOC容器中。

也就是说@AutoConfigurationPackage注解通过利用Registrar导入注册主程序所在包下所有添加@Component,@Controller....的类到IOC容器中。

这也就就解释了为啥我们在主程序包下或者子包编写配置类,Cotroller...能被自动扫描注册到容器中。

@Import

//导入注册主程序包下的组件
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";


   Class<?>[] exclude() default {};


   String[] excludeName() default {};

}

我们在来看看@Import(AutoConfigurationImportSelector.class),@Import注解的作用很简单,就是导入注册一个组件到容器中。所以关键是AutoConfigurationImportSelector.class

image-20201225163349951

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

可以看出这里的核心是getAutoConfigurationEntry(annotationMetadata);方法

image-20201225163444713

image-20201225163647363

List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);获取到的configurations的size是130。

image-20201225163745759

这130个组件是要导入到容器中的,它是哪里获取的呢??我们继续查看getCandidateConfigurations

image-20201225164047156

List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
      getBeanClassLoader());

我们继续查看loadFactoryNames方法

image-20201225164313300

我们继续查看loadSpringFactories方法

image-20201225164429108

打个断点,debug执行。

image-20201225164603247

我们可以得知它是加载FACTORIES_RESOURCE_LOCATION这个属性, 默认扫描我们当前系统里面所有META-INF/spring.factories位置文件加载组件的。

而自动配置最核心的也就是spring-boot-autoconfigure-2.4.1.jar。我们点开该jar包发现META-INF/spring.factories文件查看。

image-20201225164918363

image-20201225165129290

151-22+1=130正好是我们先前看到的加载的组件个数130个。文件里面写死了spring-boot一启动就要给容器中加载的所有配置类

按需开启自动配置项

虽然我们130个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration都是都按照条件装配规则@Conditional,最终会按需配置。

例如我们查看文件中的RabbitAutoConfiguration

image-20201225165718173

@ConditionalOnClass({ RabbitTemplate.class, Channel.class })不存在RabbitTemplate,Channel类所以以下配置都不生效。

用户自定义优先

SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先。例如WebMvcAutoConfiguration

image-20201225170624568

@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)当容器中没有WebMvcConfigurationSupport类型的组件时配置才生效。

总结

  • SpringBoot会扫描所有META-INF/spring.factories位置文件加载组件。而自动配置最核心的也就是spring-boot-autoconfigure-2.4.1.jar。所以加载的是spring-boot-autoconfigure-2.4.1.jarMETA-INF/spring.factories文件中写好的自动配置类(xxxxxAutoConfiguration)。

  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定

  • 生效的配置类就会给容器中装配很多组件

  • 只要容器中有这些组件,相当于这些功能就有了

定制化配置

  1. 用户直接自己@Bean替换底层的组件

  2. 用户去看这个组件是获取的配置文件什么值就去修改。(配置文件配置)

spring-boot-autoconfigure.jar ---> META-INF/spring.factories --->xxxxxAutoConfiguration ---> 组件 --->xxxxProperties里面拿值 ----> application.properties