zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    C语言学习笔记-静态库、动态库的制作和使用
    各种消息队列的对比
    如何使用Jupyter notebook
    Ubuntu16.04配置OpenCV环境
    Docker容器发展历史
    Ubuntu OpenSSH Server
    SpringBoot 系列文章
    SpringBoot 模板 Thymeleaf 的使用
    18、spring注解学习(AOP)——AOP功能测试
    17、spring注解学习(自动装配)——@Profile根据当前环境,动态的激活和切换一系列组件的功能
  • 原文地址:https://www.cnblogs.com/hligy/p/13883969.html
Copyright © 2011-2022 走看看