SpringBoot 自动配置

自动配置原理

流程图

图片来自参考资源链接一

ConfigurationClass

这是 Spring 用于存储 @Configuration 注解解析后的封装类,里面有带有 @Bean 注解的方法以及其他一些信息。

ConfigurationClassPostProcessor

ConfigurationClassPostProcessor 是手动注册的,根据堆栈可以看到在创建 ApplicationContext 的过程中调用到了 AnnotationConfigUtils#registerAnnotationConfigProcessors:

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
  BeanDefinitionRegistry registry, @Nullable Object source) {

 Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

 if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
  RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
  def.setSource(source);
  beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
 }

 if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
  RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
  def.setSource(source);
  beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
 }

 // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
 if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
  RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
  def.setSource(source);
  beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
 }

 return beanDefs;
}

这个过程中注册了很多 PostProcessor,包括 ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor,第一个就是用于处理配置文件的,后面两个是处理 @Autowired、@Resource 和 @Inject 的。

ConfigurationClassPostProcessor 实现了 BeanFactoryPostProcessor 接口,最终的实现是 processConfigBeanDefinitions 方法,该方法将带有 @Configuration 注解的类转换成 ConfigurationClass,最后注册到 BeanFactory 中。

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
  //...
  // 两个最重要的调用
  
  // ConfigurationClassParser parser
  parser.parse(candidates);
  //...
  // ConfigurationClassBeanDefinitionReader reader
  this.reader.loadBeanDefinitions(configClasses);
	//...
}

ConfigurationClassParser

两次解析

该类是解析的关键,上一个代码块的源码可以看到调用了 parse 方法,

public void parse(Set<BeanDefinitionHolder> configCandidates) {
  // 其他代码忽略
  // 第一次解析
  parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
  //第二次延迟解析
  this.deferredImportSelectorHandler.process();
}

第一次 parse 解析当前工程的 ConfigurationClass,因此第二次 process 延迟解析的 ConfigurationClass 就可以通过 Conditional 注解判断 starter 应该提供什么样的 Bean 了,接下来主要分析 parse 和 process 两个方法。

parse

parse 方法只解析当前工程的配置类,包括工程所有 @Configuration (当然也有 @SpringApplication 所在的类),但不包括引入的 jar 包中的。

parse 方法通过 processConfigurationClass 方法调用了 doProcessConfigurationClass,它包括对嵌套类的递归处理和 @PropertySource、@ComponentScan、@Import、@ImportResource 和 @Bean 注解的解析,最终装到 ConfigurationClass 中。

protected final SourceClass doProcessConfigurationClass(
  ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
  throws IOException {

 if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
  // 递归处理嵌套的类
  processMemberClasses(configClass, sourceClass, filter);
 }

 // Process any @PropertySource annotations

 // Process any @ComponentScan annotations

 // Process any @Import annotations
 // 处理例如 @Import(AutoConfigurationImportSelector.class)
 processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

 // Process any @ImportResource annotations

 // Process individual @Bean methods

 // Process default methods on interfaces

 // Process superclass, if any
}

其中 processImports 是自动配置的关键,方法中可以看到这样一行代码:

if (selector instanceof DeferredImportSelector) {
 this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}

意思是如果是延迟的就调用 handle,再看下 handle:

public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
 DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
 if (this.deferredImportSelectors == null) {
 // 是空的,所以一定调用不到
  DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
  handler.register(holder);
  handler.processGroupImports();
 }
 else {
  this.deferredImportSelectors.add(holder);
 }
}

而启动类 @SpringApplication 包含 @EnableAutoConfiguration,@EnableAutoConfiguration 又包含 @Import(AutoConfigurationImportSelector.class),AutoConfigurationImportSelector 类就实现了 DeferredImportSelector接口,所以最后将 AutoConfigurationImportSelector 放到了 deferredImportSelectorHandler 这个 list 中。

process

process 中最重要的方法调用是 processGroupImports:

public void processGroupImports() {
 for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
  Predicate<String> exclusionFilter = grouping.getCandidateFilter();
  grouping.getImports().forEach(entry -> {
   ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
   try {
    processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
      Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
      exclusionFilter, false);
   }
   catch (BeanDefinitionStoreException ex) {
    throw ex;
   }
   catch (Throwable ex) {
    throw new BeanDefinitionStoreException(
      "Failed to process import candidates for configuration class [" +
        configurationClass.getMetadata().getClassName() + "]", ex);
   }
  });
 }
}

该方法有两个步骤:1.getImports,找到所有引入的 jar 中的配置类;2.processImports,导入配置类。

步骤1:getImports 主要的调用顺序:getImports -> process -> getAutoConfigurationEntry,getAutoConfigurationEntry 方法如下:

public Iterable<Group.Entry> getImports() {
	for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
		this.group.process(deferredImport.getConfigurationClass().getMetadata(),
				deferredImport.getImportSelector());
	}
	return this.group.selectImports();
}

//下面都是 AutoConfigurationImportSelector 中的方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
 //...
 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
 //...
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
}

它最终 for 循环调用了之前添加到 deferredImportSelectors 的 AutoConfigurationImportSelector 类的 process 方法,再通过 SpringFactoriesLoader 获取到了所有引入的 jar 中的配置类( SpringFactoriesLoader 可以看到资源的位置 META-INF/spring.factories。)

步骤二:processImports 中有很多 ifelse,可以看到下面几行代码:

// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);

意思是如果是有 @configuration 的类就交给 processConfigurationClass,而 processConfigurationClass 就是第一次解析配置类所调用的方法。

ConfigurationClassBeanDefinitionReader

配置类的注册依赖的是 ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitions 方法,具体是 for 循环调用 loadBeanDefinitionsForConfigurationClass。

private void loadBeanDefinitionsForConfigurationClass(
  ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

 if (trackedConditionEvaluator.shouldSkip(configClass)) {
  String beanName = configClass.getBeanName();
  if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
   this.registry.removeBeanDefinition(beanName);
  }
  this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
  return;
 }

 if (configClass.isImported()) {
 	// 注册 ConfigurationClass
  registerBeanDefinitionForImportedConfigurationClass(configClass);
 }
 // 注册 @Bean
 for (BeanMethod beanMethod : configClass.getBeanMethods()) {
  loadBeanDefinitionsForBeanMethod(beanMethod);
 }
 // 注册 @ImportResource
 loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
 // 注册 @EnableConfigurationProperties
 loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

自定义 starter

创建 spring-boot-starter-gdp

  1. 首先在 resources 下新建 META-INF 目录,再新建 spring.factories 文件,添加如下配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.example.springbootstartergdp.config.GdpAutoConfiguration
  1. 新建配置类 GdpAutoConfiguration
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(GdpProperties.class)
@Import({GdpConfiguration.Family.class, GdpConfiguration.Friend.class})
public class GdpAutoConfiguration {
}
  1. @EnableConfigurationPropertie 使 spring 能自动配置 GdpProperties,GdpProperties 如下:
@ConfigurationProperties(prefix = "spring.gdp")
public class GdpProperties {
  /**
   * 用户名
   */
  private String username;
  /**
   * 密码
   */
  private String password;
  /**
   * 类型
   */
  private String type;
  //get set 不粘贴了
}
  1. @Import 导入了 GdpConfiguration.Family 和 GdpConfiguration.Friend 配置,如下:
public class GdpConfiguration {
  
  @Configuration(proxyBeanMethods = false)
  @ConditionalOnProperty(name = "spring.gdp.type", havingValue = "family")
  static class Family {

    @Bean
    @ConditionalOnMissingBean
    Chat familyChat() {
      return new FamilyChat();
    }
  }

  @Configuration(proxyBeanMethods = false)
  @ConditionalOnProperty(name = "spring.gdp.type", havingValue = "friend")
  static class Friend {

    @Bean
    @ConditionalOnMissingBean
    Chat friendChat() {
      return new FriendChat();
    }
  }
}

FamilyChat:

public class FamilyChat implements Chat {

  public String say(String info) {
    return "晚上闭灯了别玩手机!";
  }
}

目录结构:

├─src
│  ├─main
│  │  ├─java
│  │  │  └─com
│  │  │      └─example
│  │  │          └─springbootstartergdp
│  │  │              │  SpringBootStarterGdpApplication.java
│  │  │              │  
│  │  │              ├─config
│  │  │              │      GdpAutoConfiguration.java
│  │  │              │      GdpConfiguration.java
│  │  │              │      GdpProperties.java
│  │  │              │      
│  │  │              ├─listener
│  │  │              │      HelloApplicationRunListener.java
│  │  │              │      
│  │  │              └─service
│  │  │                      Chat.java
│  │  │                      FamilyChat.java
│  │  │                      FriendChat.java
│  │  │                      
│  │  └─resources
│  │      │  application.yml
│  │      │  
│  │      └─META-INF
│  │              additional-spring-configuration-metadata.json
│  │              spring.factories

使用 spring-boot-starter-gdp

引入 jar 包并添加如下配置

spring:
  gdp:
    username: mama
    password: 123456
    type: FAMILY

测试是否获取自定义 starter 中的 bean,成功输出 晚上闭灯了别玩手机!

public static void main(String[] args) {
  BeanFactory beanFactory = SpringApplication.run(StudyAutoconfigurationApplication.class, args);
  Chat family = beanFactory.getBean("familyChat", Chat.class);
  System.out.println(family.say("nihao"));
}

参考资料

AutoConfigurationImportSelector到底怎么初始化

深入理解Spring的ImportSelector接口

Spring Factories

原文地址:https://www.cnblogs.com/hligy/p/13883969.html