前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot是如何实现自动装配的

SpringBoot是如何实现自动装配的

作者头像
Java进阶之路
发布2022-08-03 17:04:35
7450
发布2022-08-03 17:04:35
举报
文章被收录于专栏:分布式与微服务

一:简述

SpringBoot作为当前最火的java开发框架,它的自动装配帮助我们省略了许多繁琐配置,能够帮助我们快速构建一个项目,那么今天我们就一起分析下SpringBoot实现自动装配的原理。

二:准备工作

Spring的自动装配是基于Spring的SPI机制和@Import注解来实现的。所以我们先简单了解下Spring的SPI机制以及@Import注解的作用。

1:SPI机制

a.什么是SPI

SPI:全称 Service Provider Interface,是一种服务发现机制,它是一种约定大于配置的思想,约定好配置文件路径,配置文件的名称,配置的定义方式等,然后可以通过配置文件的方式来加载需要的类。

注:在jdk,dubbo中也都有定义自己的SPI实现,后续的文章会讲到,感兴趣的同学也可以自己去研究下。

b. Spring中的SPI

spi是一种约定大于配置的思想,所以在使用Spring的SPI时我们需要遵守它的约定。Spring的SPI约定我们要想通过SPI来加载类,需要在ClassPath路径下的META-INF文件夹下定义一个名称为spring.factories的文件,并且它的配置文件的配置方式必须是key-value的形式进行配置。

c. Spring的SPI的使用

1.首先创建一个META-INF文件夹,并且在META-INF下创建一个spring.factories的文件。

2.在配置文件中以key—value的形式书写我们的配置(value如果多个,那么以逗号分割)。

例如:

代码语言:javascript
复制
com.example.Log=com.example.Log4J,com.example.LogBack

3.Spring已经将读取配置文件,解析配置文件的逻辑封装好了,我们只需要利用SpringFactoriesLoader工具类的api就可以获取到加载的信息。

SpringFactoriesLoader提供了两个方法获取spi的配置.

1.loadFactories()

loadFactories()方法要求读取的配置key是一个父类(或者接口)的全类名,value是这个key的子类的全类名。它可以用来获取配置value的实例。

例如:

spring.factories 配置文件:

代码语言:javascript
复制
com.example.Log=com.example.Log4J,com.example.LogBack

代码:

代码语言:javascript
复制
// Log4J,LogBack必须是Log的子类
List<Log> logs = SpringFactoriesLoader.loadFactories(Log.class, this.getClass().getClassLoader());        
//获取到配置文件value的实例
        for (Log log : logs){
            System.out.println(log);
        }

2.loadFactoryNames()

loadFactoryNames()要求key是一个全类名,而value没有限制。loadFactoryNames()可以用来获取配置value的值。

例如:

spring.factories 配置:

代码语言:javascript
复制
com.example.Log=com.example.Log4J,com.example.LogBack

代码:

代码语言:javascript
复制
List<String> classNames = SpringFactoriesLoader.loadFactoryNames(Log.class, this.getClass().getClassLoader());        
//获取到value的值 以字符串集合的方式返回
System.out.println(classNames);

2:@Import注解的作用

分为以下几种情况:

a. @Import的value属性为@Configuration的配置类或普通类

作用:将类的实例加入到Spring IoC容器中

b. @Import的value属性是 ImportSelector接口的实现类

作用:将selectImports()接口返回的类的实例加入到Spring IoC容器中

c. @Import的value属性为DeferredImportSelector接口的实现类

作用:首先会通过DeferredImportSelector的getImportGroup()方法获取的Group,然后调用Group的process()和selectImports(),将selectImports()返回的类的实例加入到Spring IoC容器中。

d. @Import的value属性是ImportBeanDefinitionRegistrar接口的实现类

作用:可以通过实现ImportBeanDefinitionRegistrar的registerBeanDefinitions()方法将需要注入的类的实例加入到Spring IoC容器中。

注:在我的另外一篇文章中有对@Import注解的作用和原理的详细说明 原文地址:@Import注解的使用和原理

三:自动装配原理分析

通过第二节的铺垫,相信大家已经对Spring的SPI和@Import注解都有了一定的了解,那么我们现在对SpringBoot的自动装配原理进行分析。

我们首先看@SpringBootApplication注解,它是一个复合注解,由@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan等注解复合而成,而自动装配是基于@EnableAutoConfiguration注解来实现的,所以我们重点分析@EnableAutoConfiguration注解。

@EnableAutoConfiguration

@EnableAutoConfiguration主要是通过@Import导入AutoConfigurationImportSelector注解实现的自动装配,而AutoConfigurationImportSelector是一个实现了DeferredImportSelector接口的类。

如果@Import注解导入的类实现了DeferredImportSelector接口,那么首先会执行它的getImportGroup()。然后会根据getImportGroup()返回的Group,执行对应Group的process()方法和selectImports()方法(如果getImportGroup()返回null,那么会使用默认的Group:DefaultDeferredImportSelectorGroup),然后将selectImports()方法返回的类的实例加入到Spring容器中。

代码语言:javascript
复制
public Class<? extends Group> getImportGroup() {    
     return AutoConfigurationGroup.class;
}

getImportGroup()方法返回AutoConfigurationGroup,所以接下来会执行AutoConfigurationGroup的process()方法和selectImports()收集需要导入的类,并且将selectImports()返回的类加入到Spring容器中。所以我们主要分析AutoConfigurationGroup的process()方法和selectImports()方法。

首先我们看process()方法做了什么

process()

process()调用getAutoConfigurationEntry方法获取到需要自动装配的类,并将它们保存在 autoConfigurationEntries中。

代码语言:javascript
复制
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
      Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
          () -> String.format("Only %s implementations are supported, got %s",
              AutoConfigurationImportSelector.class.getSimpleName(),
              deferredImportSelector.getClass().getName()));                        
              //通过getAutoConfigurationEntry()方法获取需要自动转配的类
      AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
          .getAutoConfigurationEntry(annotationMetadata);      this.autoConfigurationEntries.add(autoConfigurationEntry);      
          for (String importClassName : autoConfigurationEntry.getConfigurations()) {        
          this.entries.putIfAbsent(importClassName, annotationMetadata);
      }
    }

getAutoConfigurationEntry()

getAutoConfigurationEntry()通过getCandidateConfigurations()方法获取自动装配的类,然后会进行去重,过滤不需要导入的类(包括application.properties文件中的spring.autoconfigure.exclude配置的类,@SpringBootApplication注解配置的exclude和excludeName的类,AutoConfigurationImportFilter的match()方法返回true的类),然后将处理完的类封装成AutoConfigurationEntry。

代码语言:javascript
复制
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {    
    if (!isEnabled(annotationMetadata)) {      
       return EMPTY_ENTRY;
    }
    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);                
    //获取spring.factories中AutoConfigurationImportFilter的实现类并且循环调用它们的match()方法
    configurations = getConfigurationClassFilter().filter(configurations);                
    //发布自动装配的事件
    fireAutoConfigurationImportEvents(configurations, exclusions);                
    //封装成AutoConfigurationEntry返回
    return new AutoConfigurationEntry(configurations, exclusions);
  }

getCandidateConfigurations()

getCandidateConfigurations()方法通过SPI的方式获取key为EnableAutoConfiguration全类名的value。

代码语言:javascript
复制
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {                
//通过spi机制获取需要自动装配的类
    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;
  }
代码语言:javascript
复制
protected Class<?> getSpringFactoriesLoaderFactoryClass() {    
        return EnableAutoConfiguration.class;
}

selectImports()

代码语言:javascript
复制
public Iterable<Entry> selectImports() {      
      if (this.autoConfigurationEntries.isEmpty()) {        
        return Collections.emptyList();
      }                        
      //获取所有的需要排除的类
      Set<String> allExclusions = this.autoConfigurationEntries.stream()
          .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());                        
          //获取需要导入的类
      Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
          .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
          .collect(Collectors.toCollection(LinkedHashSet::new));
      processedConfigurations.removeAll(allExclusions);                        
      //根据AutoConfigureOrder进行排序 然后将导入的类封装成Entry返回。
      return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
          .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
          .collect(Collectors.toList());
    }

Spring在处理@Import注解的时候会将selectImports()方法返回的类的实例加入到Spring容器中,也就完成了自动装配。

四:总结

自动装配主要是根据@Import注解和SPI机制来完成的,所以要理解自动装配首先需要了解@Import注解和SPI机制。大致的流程就是通过SPI机制获取到spring.factories中key为EnableAutoConfiguration的value(需要自动装配的全类名),然后将获取到的类名通过@Import注解将对应的实例加入到Spring容器中。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java进阶之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档