写点什么

自定义 spring boot starter 三部曲之三:源码分析 spring.factories 加载过程

作者:程序员欣宸
  • 2022 年 7 月 15 日
  • 本文字数:4330 字

    阅读完需:约 14 分钟

自定义spring boot starter三部曲之三:源码分析spring.factories加载过程

欢迎访问我的 GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos


  • 本文是《自定义 spring boot starter 三部曲》系列的终篇,前文中我们开发了一个 starter 并做了验证,发现关键点在于 spring.factories 的自动加载能力,让应用只要依赖 starter 的 jar 包即可,今天我们来分析 Spring 和 Spring boot 源码,了解 spring.factories 自动加载原理;

三部曲文章链接

  1. 《自定义spring boot starter三部曲之一:准备工作》

  2. 《自定义spring boot starter三部曲之二:实战开发》

版本情况

  • 本文中涉及到的库的版本:


  1. Spring boot :1.5.9.RELEASE;

  2. JDK :1.8.0_144

初步分析

  • 先回顾 customizeservicestarter 模块中 spring.factories 文件的内容:


org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.bolingcavalry.customizeservicestarter.CustomizeConfiguration
复制代码


  • 从上述内容可以确定今天源码学习目标:


  1. spring 容器如何处理配置类;

  2. spring boot 配置类的加载情况;

  3. spring.factories 中的 EnableAutoConfiguration 配置何时被加载?

  4. spring.factories 中的 EnableAutoConfiguration 配置被加载后做了什么处理;

spring 容器如何处理配置类

  • ConfigurationClassPostProcessor 类的职责是处理配置类;

  • ConfigurationClassPostProcessor 是 BeanDefinitionRegistryPostProcessor 接口的实现类,它的 postProcessBeanDefinitionRegistry 方法在容器初始化阶段会被调用(BeanDefinitionRegistryPostProcessor 接口的更多细节请参考《spring4.1.8扩展实战之六:注册bean到spring容器(BeanDefinitionRegistryPostProcessor接口)》);

  • postProcessBeanDefinitionRegistry 方法又调用 processConfigBeanDefinitions 方法处理具体业务;

  • processConfigBeanDefinitions 方法中通过 ConfigurationClassParser 类来处理 Configuration 注解,如下图:



  • 如上图红框所示,所有被 Configuration 注解修饰过的类,都会被 parser.parse(candidates)处理,即 ConfigurationClassParser 类的 parse 方法;

  • parse 方法中调用 processDeferredImportSelectors 方法做处理:找到 Configuration 类中的 Import 注解,对于 Import 注解的值,如果实现了 ImportSelector 接口,就调用其 selectImports 方法,将返回的名称实例化:


private void processDeferredImportSelectors() {    //这里就是Configuration注解中的Import注解的值,    //例如EnableAutoConfiguration注解的源码中,Import注解的值是EnableAutoConfigurationImportSelector.class    List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;    this.deferredImportSelectors = null;    Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);
for (DeferredImportSelectorHolder deferredImport : deferredImports) { ConfigurationClass configClass = deferredImport.getConfigurationClass(); try { //以EnableAutoConfiguration注解为例,其Import注解的值为EnableAutoConfigurationImportSelector.class, //那么此处就是在调用EnableAutoConfigurationImportSelector的selectImports方法,返回了一个字符串数组 String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata()); //字符串数组中的每个字符串都代表一个类,此处做实例化 processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", ex); } } }
复制代码


  • 小结一下 spring 容器配置类的逻辑:


  1. 找出配置类;

  2. 找出配置类中的 Import 注解;

  3. Import 注解的值是 class,如果该 class 实现了 ImportSelector 接口,就调用其 selectImports 方法,将返回的名称实例化;


  • 有了上面的结论就可以结合 Spring boot 的源码来分析加载了哪些数据了;

spring boot 配置类的加载情况

  • 我们的应用使用了 SpringBootApplication 注解,看此注解的源码,使用了 EnableAutoConfiguration 注解:


@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 {  ......
复制代码


  • EnableAutoConfiguration 注解中,通过 Import 注解引入了 EnableAutoConfigurationImportSelector.class:


@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(EnableAutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration {  ......
复制代码


  • 看 EnableAutoConfigurationImportSelector 的源码:


/** * {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration * auto-configuration}. This class can also be subclassed if a custom variant of * {@link EnableAutoConfiguration @EnableAutoConfiguration}. is needed. * * @deprecated as of 1.5 in favor of {@link AutoConfigurationImportSelector} * @author Phillip Webb * @author Andy Wilkinson * @author Stephane Nicoll * @author Madhura Bhave * @since 1.3.0 * @see EnableAutoConfiguration */@Deprecatedpublic class EnableAutoConfigurationImportSelector    extends AutoConfigurationImportSelector {
@Override protected boolean isEnabled(AnnotationMetadata metadata) { if (getClass().equals(EnableAutoConfigurationImportSelector.class)) { return getEnvironment().getProperty( EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true); } return true; }
}
复制代码


  • 上述源码有三处重点需要关注:

  • 第一,EnableAutoConfigurationImportSelector 是 AutoConfigurationImportSelector 的子类;

  • 第二,EnableAutoConfigurationImportSelector 已经被废弃了,不推荐使用;

  • 第三,文档中已经写明废弃原因:从 1.5 版本开始,其特性由父类 AutoConfigurationImportSelector 实现;

  • 查看 AutoConfigurationImportSelector 的源码,重点关注 selectImports 方法,该方法的返回值表明了哪些类会被实例化:


@Override  public String[] selectImports(AnnotationMetadata annotationMetadata) {    if (!isEnabled(annotationMetadata)) {      return NO_IMPORTS;    }    try {        //将所有spring-autoconfigure-metadata.properties文件中的键值对保存在autoConfigurationMetadata中      AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader          .loadMetadata(this.beanClassLoader);      AnnotationAttributes attributes = getAttributes(annotationMetadata);      //取得所有配置类的名称      List<String> configurations = getCandidateConfigurations(annotationMetadata,          attributes);      configurations = removeDuplicates(configurations);      configurations = sort(configurations, autoConfigurationMetadata);      Set<String> exclusions = getExclusions(annotationMetadata, attributes);      checkExcludedClasses(configurations, exclusions);      configurations.removeAll(exclusions);      configurations = filter(configurations, autoConfigurationMetadata);      fireAutoConfigurationImportEvents(configurations, exclusions);      return configurations.toArray(new String[configurations.size()]);    }    catch (IOException ex) {      throw new IllegalStateException(ex);    }  }
复制代码


  • 通过上述代码可以发现,getCandidateConfigurations 方法的调用是个关键,它返回的字符串都是即将被实例化的类名,来看此方法源码:


protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,      AnnotationAttributes attributes) {    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;  }
复制代码


  • getCandidateConfigurations 方法中,调用了静态方法 SpringFactoriesLoader.loadFactoryNames,上面提到的 SpringFactoriesLoader.loadFactoryNames 方法是关键,看看官方文档对此静态方法的描述,如下图红框所示,该方法会在 spring.factories 文件中寻找指定接口对应的实现类的全名(包名+实现类):



  • 在 getCandidateConfigurations 方法中,调用 SpringFactoriesLoader.loadFactoryNames 的时候传入的指定类型是 getSpringFactoriesLoaderFactoryClass 方法的返回值:


protected Class<?> getSpringFactoriesLoaderFactoryClass() {    return EnableAutoConfiguration.class;  }
复制代码


  • 现在可以梳理一下了:

  • spring boot 应用启动时使用了 EnableAutoConfiguration 注解;

  • EnableAutoConfiguration 注解通过 import 注解将 EnableAutoConfigurationImportSelector 类实例化,并且将其 selectImports 方法返回的类名实例化后注册到 spring 容器;

  • EnableAutoConfigurationImportSelector 的 selectImports 方法返回的类名,来自 spring.factories 文件内的配置信息,这些配置信息的 key 等于 EnableAutoConfiguration;

  • 现在真相大白了:只要我们在 spring.factories 文件内配置了 EnableAutoConfiguration,那么对于的类就会被实例化后注册到 spring 容器;

  • 至此,《自定义 spring boot starter 三部曲》系列就完结了,希望实战加源码分析的三篇文章,能帮助您理解和实现自定义 starter 这种简单快捷的扩展方式;

欢迎关注 InfoQ:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...

发布于: 2022 年 07 月 15 日阅读数: 48
用户头像

搜索"程序员欣宸",一起畅游Java宇宙 2018.04.19 加入

前腾讯、前阿里员工,从事Java后台工作,对Docker和Kubernetes充满热爱,所有文章均为作者原创,个人Github:https://github.com/zq2599/blog_demos

评论

发布
暂无评论
自定义spring boot starter三部曲之三:源码分析spring.factories加载过程_Java_程序员欣宸_InfoQ写作社区