写点什么

SpringBoot 自动装配原理,一文掌握!

作者:程序员小毕
  • 2023-01-03
    湖南
  • 本文字数:5950 字

    阅读完需:约 20 分钟

SpringBoot 自动装配原理,一文掌握!

本文详细讲解了 SpringBoot 自动装配原理,可以直接拉到最后看总结。由于 Spring 源码比较复杂,是需要一些基础的。

如果有不懂的地方,欢迎提问!


Spring Boot 一个很大的特点就是极大的简化了原来在 Spring 中复杂的 XML 文件配置过程,让我们对 Spring 应用的搭建和开发变得极其简单。既然可以简化配置,那就意味着很多配置都是需要默认的,这也使其提出了约定大于配置和自动装配的思想。一些通用的配置会默认设置好,整个组件需要的时候直接载入,不需要的时候可以整个卸载。

通过 Spring Boot 我们可以很方便的引入新的组件,只需要在依赖文件中加入对应的 xxx-starter 即可,然后把一些必要的配置比如 url 信息做个简单的设置,或者增加一个 @EnableXXX,就可以开始使用了。

这里以 Feign 为例:

<dependency>   <groupId>org.springframework.cloud</groupId>   <artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
复制代码


@SpringBootApplication@EnableFeignClientspublic class SampleApplication {
   public static void main(String[] args) {      SpringApplication.run(SampleApplication.class, args);   }}
复制代码

只需要这样,就可以将 Feign 引入项目中了,接下来根据自己的需要定义对应的 Feign client 即可,是不是非常简单。


Spring Boot 到底是如何做到的呢?


相关视频解析:自动装配原理

自动装配原理

自动装配的入口

自动装配的基础,是 Spring 从 4.x 版本开始支持 JavaConfig,让开发者可以免去繁琐的 xml 配置形式,而是使用熟悉的 Java 代码加注解,通过 @Configuration、@Bean 等注解可以直接向 Spring 容器注入 Bean 信息。

那么就有种设想,如果我把一些必须的 Bean 以 Java 代码方式准备好呢,只需要引入对应的配置类,相应的 Bean 就会被加载到 Spring 容器中。所以,有了这个基础 Spring Boot 就有了实现自动装配的可能。

还是以 Feign 为例,FeignAutoConfiguration 这个类就是一个 Feign 的自动装配类,我们来探究一下他是如何生效的。

@Configuration@ConditionalOnClass(Feign.class)@EnableConfigurationProperties({ FeignClientProperties.class,      FeignHttpClientProperties.class })public class FeignAutoConfiguration {
   @Autowired(required = false)   // 注入了一堆FeignClientSpecification   private List<FeignClientSpecification> configurations = new ArrayList<>();
   @Bean   public HasFeatures feignFeature() {      return HasFeatures.namedFeature("Feign", Feign.class);   }
   @Bean   public FeignContext feignContext() {      FeignContext context = new FeignContext();      context.setConfigurations(this.configurations);      return context;   }}
复制代码

在每个 Spring Boot 的启动类上,都会有这样一个复合注解 @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 {}
……省略其他注解@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration {}
复制代码

上面四个都是一些通用注解,关键在于下面的 @EnableAutoConfiguration注解,从名字就可以看出来,它可以开启自动配置。在之前一篇文章我们已经讲过,通过 @Import 注解我们可以导入一些自定义的 BeanDefination 信息,或者导入一些配置类。在 EnableAutoConfiguration 内部,它使用 @Import 标注了 AutoConfigurationImportSelector。

可以看出,它实现了 ImportSelector 接口,之前我们已经在讲结果 @Import 注解是如何生效的。

下面,先对后面涉及的一些类和文件做一个简单介绍。


ImportSelector

看名字就可以知道,这是用于导入的选择器。String[] selectImports() 方法会返回需要导入的配置类的全路径名。在 Spring 容器启动的过程中会调用 invokeBeanFactoryPostProcessors,然后会执行一个重要的后置处理器 ConfigurationClassPostProcessor ,完成配置类的解析,这里会处理 ImportSelector 返回的这些类,将其加载到容器中。

public interface ImportSelector {   String[] selectImports(AnnotationMetadata importingClassMetadata);}
复制代码

DeferredImportSelector

DeferredImportSelector 继承了 ImportSelector,它的作用是用于延迟导入。在所有的需要处理的配置类解析过程中,继承此接口的解析排在最后,并且在有多个 DeferredImportSelector 实现类的情况下,可以继承 Ordered 实现排序的效果。

public interface DeferredImportSelector extends ImportSelector {
   @Nullable   default Class<? extends Group> getImportGroup() {      return null;   }
   interface Group {   }}
复制代码


继续之前的内容,AutoConfigurationImportSelector 实现了 DeferredImportSelector 接口,所

在执行 ConfigurationClassParser.processImports()方法的时候,最终会调用到下面这段逻辑。一般继承 ImportSelector 会执行其 selectImport 方法。但是这里不同的是,它还继承了 DeferredImportSelector 接口,对 ImportSelector 只是间接继承。在 processImports() 方法中有这样的额外判断,如果是 DeferredImportSelector 的子类,将会执行 deferredImportSelectorHandler.handle(),最终会回调 AutoConfigurationImportSelector 的 process 方法。具体的调用过程请见下图。

Spring Boot 2.x 的版本与 1.x 有所不同,1.x 是回调 selectImports 方法。

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,      Collection<SourceClass> importCandidates, boolean checkForCircularImports) {…… if (candidate.isAssignable(ImportSelector.class)) { ……    // 执行这段逻辑 if (selector instanceof DeferredImportSelector) {  this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); } else { ……  processImports(configClass, currentSourceClass, importSourceClasses, false); } ……}
复制代码


读取配置

在 process() 方法中主要做了一件事,读取并解析 spring.factories 配置文件中的信息,将这些配置文件对应的全路径类名都放入 AutoConfigurationEntry 集合中。接下来详细解释相关逻辑。


getAutoConfigurationMetadata() 方法读取并解析了 spring-autoconfigure-metadata.properties 文件,用于控制自动装配条件。关于这个路径信息,追踪方法可以找到,比较简单。

protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";
复制代码

getAutoConfigurationEntry() 方法会获取需要自动装配的类和需要排除的类,读取的文件是 META-INF/spring.factories


关于这个文件路径是怎么指定的,可以在下面方法中看到。一直深入追踪,在 loadSpringFactories 方法中,会加载 META-INF/spring.factories 路径下的配置内容,并且这个路径是硬编码写死的。在全部读取完毕之后,会放在一个 Map 中,key 为类名,value 为对应的自定义配置类。getSpringFactoriesLoaderFactoryClass()  方法会固定返回 EnableAutoConfiguration.class,所以这里只会返回 EnableAutoConfiguration 对应的配置内容,配置文件内容如下图。


public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {   ……   // getAutoConfigurationMetadata() 方法读取并解析了 spring-autoconfigure-metadata.properties 文件,用于控制自动装配条件   // AutoConfigurationEntry 方法会获取需要自动装配的类和需要排除的类   AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)         .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);   // 添加到 AutoConfigurationEntry集合中等待加载   this.autoConfigurationEntries.add(autoConfigurationEntry);   for (String importClassName : autoConfigurationEntry.getConfigurations()) {      this.entries.putIfAbsent(importClassName, annotationMetadata);   }}
// 返回自动配置的类名,加载Spring.factories中的配置信息protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {   // loadFactoryNames 会读取对应的配置文件,位置在META-INF/spring.factories中  // getSpringFactoriesLoaderFactoryClass 返回 EnableAutoConfiguration.class   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),         getBeanClassLoader());  ……   return configurations;}
复制代码

具体执行方法的调用链路如下:


加载配置

到这里我们就明白,spring.properties 文件中的配置类是如何加载的,但是问题来了,他什么时候注册到 Spring 容器中呢?

回到之前执行过程中的processGroupImports 方法(在前面的图片已用红框标注了出来),这里会调用 getImports 拿到配置类信息,然后再次调用类信息,然后递归调用 processImports,这个方法之前的文章已经解释过了,如果是配置类会解析并注册 Spring 的 Bean 信息


public void processGroupImports() {   for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {     // getImports 得到解析后的类名      grouping.getImports().forEach(entry -> {         ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());         try {           // 再次调用 processImports 递归处理,对配置类解析,注册为 Bean            processImports(configurationClass, asSourceClass(configurationClass),                  asSourceClasses(entry.getImportClassName()), false);         }         catch (BeanDefinitionStoreException ex) {           ……         }      });   }}
复制代码

另外,再额外说一下 getImports 方法。之前 process 方法并没有返回值,而是把配置信息都保存在了 autoConfigurationEntries 中,所以在执行完 process 之后会紧接着执行 selectImports()。它的功能主要是排除需要排除的类信息,并且在这里按照 spring-autoconfigure-metadata.properties 中指定的顺序排序,然后再返回类信息。

public Iterable<Group.Entry> getImports() {   for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {   // 调用 process 逻辑      this.group.process(deferredImport.getConfigurationClass().getMetadata(),            deferredImport.getImportSelector());   }   return this.group.selectImports();}
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); // 将需要加载的类排序返回,排序规则按照 spring-autoconfigure-metadata.properties 中指定的顺序 return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()   .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))   .collect(Collectors.toList());}
复制代码


总结

最后,我们再次总结一下整个自动装配的过程。

  1. 引入 META-INF/spring.factories 配置文件,在 EnableAutoConfiguration 对应的 value 中配置需要引入的配置类。

  2. 启动类增加 @EnableAutoConfiguration 注解,@SpringBootApplication 已经自带。

  3. @EnableAutoConfiguration 注解中通过 @Import 标注了 AutoConfigurationImportSelector 类。

  4. AutoConfigurationImportSelector 继承了 DeferredImportSelector 接口,在 Spring 生命周期处理 BeanFactoryPostProcessors 的时候会对配置信息进行后置处理,这是会调用到 AutoConfigurationImportSelector.process 方法。

  5. process 方法中会读取 META-INF/spring.factories 配置文件中的内容为 Key-Value 形式,读取完后值返回 key = EnableAutoConfiguration 对应的配置类信息,保存到 autoConfigurationEntries 中。

  6. AutoConfigurationGroup.selectImports 方法返回排序、筛选后的配置类信息,然后依次遍历,递归调用 processImports, 根据这些配置类的全路径名读取并注册在 Spring 容器中。

原文:https://mp.weixin.qq.com/s/1zdk-Gqh5JcZaUil-JWCUA

如果感觉本文对你有帮助,点赞关注支持一下,想要了解更多 Java 后端,大数据,算法领域最新资讯可以关注我公众号【架构师老毕】私信 666 还可获取更多 Java 后端,大数据,算法 PDF+大厂最新面试题整理+视频精讲

用户头像

领取资料添加小助理vx:bjmsb2020 2020-12-19 加入

Java领域;架构知识;面试心得;互联网行业最新资讯

评论

发布
暂无评论
SpringBoot 自动装配原理,一文掌握!_源码_程序员小毕_InfoQ写作社区