SpringBoot之自动配置原理剖析

2,246 阅读7分钟

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

说明:文中源码为SpringBoot2.7.6版本

1、什么是SpringBoot的自动配置

1.1 定义

SpringBoot自动配置,英文是Auto-Configuration:

  1. 它是基于你引入的依赖jar包,对SpringBoot应用进行自动配置

  2. 它为SpringBoot框架的“开箱即用”提供了基础支撑

1.2 区分自动配置和自动装配

区分自动配置和自动装配

自动配置:Auto-Configuration,针对的是SpringBoot中的配置类

自动装配:Autowire,针对的是Spring中的依赖注入

1.3 理解配置类

配置类英文是:Configuration Class

广义的“配置类”:被注解@Component直接或间接修饰的某个类,即我们常说的Spring组件,其中包括了@Configuration类

狭义的“配置类”:特指被注解@Configuration所修饰的某个类,又称为@Configuration类无特殊说明所说的配置类为广义的配置类

1.4 配置类的示例

@Configuration
public class Constant {
    @Bean
    public String beanData() {
        return "bean";
    }
}

@Component
@ConfigurationProperties(prefix = "simple")
public class SimpleProperties {
    private String env;
    public String getEnv() {
        return env;
    }
    public void setEnv(String env) {
        this.env = env;
    }
}

2、SpringBoot自动配置的示例 - Redis

2.1 引入依赖

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.2 配置Redis

application.yml

spring:
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    password:

2.3 使用Redis

@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Autowired
private StringRedisTemplate springRedisTemplate;

3、SpringBoot的启动流程

3.1 SpringBoot启动的简化版代码

public static void run(Class<?> primaryClass) {
	// 1. 创建一个ApplicationContext实例,即IoC容器
  ApplicationContext contet = createApplicationContext();

  // 2. 将主类(primaryClass)注册到IoC容器中
  loadSourceClass(context, primaryClass);

  // 3. 递归加载并处理所有的配置类
  processConfigurationClasses(context);

  // 4. 实例化所有的单例Bean(Singleton Bean)
  instantiateSingletonBeans(context);

  // 5. 如果是Web应用,则启动Web服务器
  startWebServer(context); 
}

3.2 SpringBoot启动的流程

image.png

说明:

  1. 第2步,源配置类:通常是main方法所在的类,而且会被注解@SpringBootApplication所修饰,又称之为主类,这一步很简单但是又很重要,它是遍历所有配置类的起点;
  2. 第3步,SpringBoot会自动找到所有的配置类,然后加载和处理它们,“自动配置”就属于其中一环;
  3. 第4步,实例化所有的单例Bean,“依赖注入”和“自动装配”就属于其中的环节;

3.3 加载配置类的简化版代码1

public static void processConfigurationClasses(ApplicationContext context) {
  // 1. 首先从IoC容器中取出当前存在的源配置类
  Class<?> sourceConfigurationClass = getSourceConfigurationClass(context);

  // 2. 创建一个配置类解析器,然后递归加载并处理应用中所有的配置类
  ConfigClassParser parser = new ConfigClassParser(context);
  parser.parse(sourceConfigurationClass);

  // 3.1 向IoC容器中注册@Bean方法对应的BeanDefinition
  loadBeanDefinitionsFromBeanMethods(parser.configurationClasses);

  // 3.2 向IoC容器中注册ImportBeanDefinitionRegistrar导入的BeanDefinition
  loadBeanDefinitionsFromRegistrars(parser.configurationClasses);
}

3.4 加载配置类的简化版代码2

public void parse(Class<?> configClass) {
  // 1. 处理@ComponentScan:根据@ComponentScan扫描指定的package,得到一系列配置类
  if (hasComponentScan(configClass)) {
    for (Class<?> clazz : doScan(configClass)) {
      // 递归处理
      this.parse(clazz);
    }
  }

  // 2. 处理注解@Import:根据注解@Import,得到一系列被导入的配置类
  if (hasIimportedClasses(configClass)) {
    for (Class<?> clazz : getImports(configClass)) {
      // 递归处理
      this.parse(clazz);
    }
  }

  // 3. 处理@Bean方法
  processBeanMethods(configClass);

  // 4. 处理@Import导入的ImportBeanDefinitionRegistrar
  processRegistrars(configClass);

  // 5. 加入到一个全局的配置类集合中
  this.configurationClasses.add(configClass);
}

3.5 加载配置类的流程

image.png

这一步涉及到了两个重要的注解@ComponentScan和@Import,SpringBoot使用注解@Import导入一个ImportSelector从而实现了自动配置功能,下面从源码了解一下。

4、一切都从注解@SpringBootApplication说起

4.1 注解@SpringBootApplication源码

每个SpringBoot项目的都有一个启动类,该类关键的一点是要有注解@SpringBootApplication

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

下面我们就看一下注解@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 {
	......
}

再看一下注解@SpringBootConfigiguration

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

再看一下注解@EnableAutoConfiguration

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

说明:

  1. 其中AutoConfigurationImportSelector的含义是开启了自动配置的功能

4.2 注解@SpringBootApplication结构图

image.png

说明

  1. @SpringBootApplication修改的类,也会被@Configuration间接修饰,即“源配置类”
  2. SpringBoot框架会对“源配置类”所在的package进行组件扫描(Component Scan)
  3. SpringBoot框架最终会导入AutoConfigurationImportSelector来实现自动配置

5、如何实现AutoConfigurationImportSelector类

AutoConfigurationImportSelector应该如何实现,才能优雅地去发现classpath中的jar包的自动配置类?

所谓“优雅”,指的是用记只需引入jar包即可,至于jar包中有哪些自动配置类、类名是什么等乖这些信息都不用关心。这个特性和Java SPI类似,Spring框架中的SpringFactories机制,它就是Java SPI设计思想的延伸和扩展。

5.1 SpringFactories机制

  1. Java SPI机制的延伸和扩展
  2. Spring框架的基础机制,在Spring以及SpringBoot源码中到处可见
  3. 可以基于它来实现SpringBoot的自动配置功能
  4. 它的核心逻辑是从classpath中读取到所有jar包中的配置文件META-IF/spring.factories,然后根据指定的key从配置文件中解析出对应的value值

5.2 Java SPI机制 vs SpringFactories机制

Java SPI机制SpringFactories机制
使用约定的配置文件:
文件路径是META-IF/services/<Service接口全限定名>
文件内容是Service Provider类的全限定名。多个Provider类的话,每个类占据一行
使用约定的配置文件
文件路径是META-IF/spring.factories
文件内容是"key=value1,valu2,...valueN"的格式。其中key是指定的某个类名,value是逗号隔开的多个类名
第三方jar包,负责提供配置文件:
高内聚低耦合,代码配置一肩挑
使用ClassLoader来读取classpath的配置文件
同左
通过ServiceLoader,返回一个ServiceProvider的对象实例集合通过类SpringFactoriesLoader,返回一个类名的集合,可以根据实际需求对这些类名进行下一步的处理

5.3 AutoConfigurationImportSelector关键源码

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
  if (!isEnabled(annotationMetadata)) {
    return NO_IMPORTS;
  }
  // SpringBoot自动配置的入口方法
  AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
  return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   // 1. 获取 annotationMetadata 的注解 @EnableAutoConfiguration 的属性
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   // 2. 从资源文件 spring.factories 中获取 @EnableAutoConfiguration 对应的所有的类
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   configurations = removeDuplicates(configurations);
   // 3. 通过在注解 @EnableAutoConfiguration 设置 exclude 的相关属性,可以排除指定的自动配置类
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
   // 4. 根据注解 @Conditional 来判断是否需要排除某些自动配置类
   configurations = getConfigurationClassFilter().filter(configurations);
   // 5. 触发 AutoConfiguration 导入的相关事件
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  // 通过 SpringFactories 机制,从配置文件 spring.factories 中批出所有的自动配置类
  List<String> configurations = new ArrayList<>(
    SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
  ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
  Assert.notEmpty(configurations,
                  "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
                  + "are using a custom packaging, make sure that file is correct.");
  return configurations;
}

5.4 注解@Conditional

@Conditional,是来自Spring框架的一个注解

  1. 它的作用是实现:只有在特定条件满足时,才会向IoC容器注册指定的组件
  2. 可以将@Conditional理解为某种情况下的IF语句

Spring框架提供了一系列常用的Conditional扩展注解,如下

注解名称作用
@ConditionalOnBean当容器中存在指定的Bean时,满足条件
@ConditionalOnMissingBean当容器中不存在指定的Bean时,满足条件
@ConditionalOnClass当classpath中存在指定的类时,满足条件
@ConditionalOnMissingClass当classpath中不存在指定的类时,满足条件
@ConditionalOnProperty当指定的属性具备指定的值时,满足条件
@ConditionalOnWebApplication当应用程序是web应用时,满足条件

5.5 AutoConfigurationImportSelector实现流程

image.png

说明:

  1. 第1步,通过ClassLoader去获取classpath中的配置文件META-INF/spring.factories
  2. 第2步,在所有的配置文件META-INF/spring.factories中,筛选出以EnableAutoConfiguration.class为key的、符合条件的配置类
  3. 第3步,注解@Conditional,提供了一种灵活的过滤机制

5.6 小结

image.png