springboot自动装配的源码与流程图

 更新时间:2021年08月04日 10:14:00   作者:迷恋着你微笑的人zz  
在日常的开发过程中Spring Boot自动装配的特性给我们开发减少了很多重复性的工作,这篇文章主要给大家介绍了关于springboot自动装配的相关资料,需要的朋友可以参考下

前言

在使用SpringBoot开发项目中,遇到一些 XXX-XXX-starter,例如mybatis-plus-boot-starter,这些包总是能够自动进行配置,
减少了开发人员配置一些项目配置的时间,让开发者拥有更多的时间用于开发的任务上面。下面从源码开始。

正文

SpringBoot版本:2.5.3

  1. 从@SpringBootApplication进入@EnableAutoConfiguration
  2. 然后进入AutoConfigurationImportSelector
    @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 {}

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {}

进入AutoConfigurationImportSelector,可以发现该类是ImportSelector接口的实现类,然后直接定位至selectImports方法。

到了这里,其实主动装配的套路就是@EnableXXX加@Import的套路。这就是一个大概的认知了。

    @Override
   public String[] selectImports(AnnotationMetadata annotationMetadata) {
      if (!isEnabled(annotationMetadata)) {
         return NO_IMPORTS;
      }
      AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
      return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
   }

下面进入getAutoConfigurationEntry方法:

        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);
            configurations = getConfigurationClassFilter().filter(configurations);
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationEntry(configurations, exclusions);
        }

详细的注释已经写在代码中的了,这里面最重要的是getCandidateConfigurations方法,其次是下面的过滤排除不需要的配置信息。下面进入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;
        }

        protected Class<?> getSpringFactoriesLoaderFactoryClass() {
              return EnableAutoConfiguration.class;
        }

        protected ClassLoader getBeanClassLoader() {
              return this.beanClassLoader;
        }

这里需要关注的方法是SpringFactoriesLoader.loadFactoryNames,进入该方法,该方法是一个静态方法。

   public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
      ClassLoader classLoaderToUse = classLoader;
      if (classLoaderToUse == null) {
         classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
      }
        // 获取类名
      String factoryTypeName = factoryType.getName();
      return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
   }

   private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        // 查询缓存
      Map<String, List<String>> result = cache.get(classLoader);
      if (result != null) {
         return result;
      }
      result = new HashMap<>();
      try {
            // 1. 获取类加载器能读取的所有在META-INF目录下的spring.factories文件
         Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
         while (urls.hasMoreElements()) {
            // 遍历路径
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
                // 将路径下的文件数据读取为Properties
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            // 遍历Properties
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
               String factoryTypeName = ((String) entry.getKey()).trim();
               String[] factoryImplementationNames =
                     StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
               for (String factoryImplementationName : factoryImplementationNames) {
                  result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                        .add(factoryImplementationName.trim());
               }
            }
         }
         // Replace all lists with unmodifiable lists containing unique elements
            // 用不可修改列表替换
         result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
               .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
            // 加入缓存
         cache.put(classLoader, result);
      }
      catch (IOException ex) {
         throw new IllegalArgumentException("Unable to load factories from location [" +
               FACTORIES_RESOURCE_LOCATION + "]", ex);
      }
      return result;
   }
  1. loadSpringFactories方法就是加载并读取所传入的类加载器能读取的所有spring.factories文件,并将读取的数据最终转换为Map<String, List>类型的数据,然后存入本地缓存。
  2. loadFactoryNames方法就是通过传入factoryType(也就是calss.name)来获取数据,并不是获取所有的数据。所以这里会有很多地方会用到。
  3. @EnableAutoConfiguration是取的文件中的org.springframework.boot.autoconfigure.EnableAutoConfiguration,所以一般自定义starter的自动配置文件都是在这个key后面。例如:org.springframework.boot.autoconfigure.EnableAutoConfiguration= a.b.c.d.XXX;

至此,源码就差不多完成了,其实从源码上来看其实也不难。

大概流程图如下:

最后

说了这么多源码,也画了流程图。这里面最重要的就是SpringFactoriesLoader,从这个类名就可以看出来,是专门处理factories文件的,这个类只提供了两个静态方法,而EnableAutoConfiguration只是取了其中的EnableAutoConfiguration下的数据,那么其它的数据呢,不会用到吗?肯定不会的,所以spring在很多地方会用到这个类,后面看spring源码的时候在来看吧,这里先标记一下。

还有就是,SpringFactoriesLoader和JDK的SPI也是差不多的一个思想。下一篇就来看看JDK的SPI

到此这篇关于springboot自动装配的源码与流程图的文章就介绍到这了,更多相关springboot自动装配内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java二叉树的几种遍历递归与非递归实现代码

    java二叉树的几种遍历递归与非递归实现代码

    这篇文章主要介绍了java二叉树的几种遍历递归与非递归实现代码,需要的朋友可以参考下
    2020-12-12
  • Springboot集成RabbitMQ报错及解决

    Springboot集成RabbitMQ报错及解决

    这篇文章主要介绍了Springboot集成RabbitMQ报错及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • IDEA启动报错Internal error. Please refer to https://jb.gg/ide/critical-startup-errors解决办法

    IDEA启动报错Internal error. Please refer to https://jb.gg/i

    这篇文章主要介绍了IDEA启动报错Internal error. Please refer to https://jb.gg/ide/critical-startup-errors解决办法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-04-04
  • java 简单的计算器程序实例代码

    java 简单的计算器程序实例代码

    这篇文章主要介绍了java 简单的计算器程序实例代码的相关资料,需要的朋友可以参考下
    2017-06-06
  • 基于SpringMVC @RequestMapping的参数和用法

    基于SpringMVC @RequestMapping的参数和用法

    这篇文章主要介绍了SpringMVC @RequestMapping的参数和用法解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • 深入解析Java的设计模式编程中建造者模式的运用

    深入解析Java的设计模式编程中建造者模式的运用

    这篇文章主要介绍了深入解析Java的设计模式编程中建造者模式的运用,同时文中也介绍了建造者模式与工厂模式的区别,需要的朋友可以参考下
    2016-02-02
  • Java可重入锁的实现原理与应用场景

    Java可重入锁的实现原理与应用场景

    今天小编就为大家分享一篇关于Java可重入锁的实现原理与应用场景,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • Java有效处理异常的三个原则

    Java有效处理异常的三个原则

    Java中异常提供了一种识别及响应错误情况的一致性机制,有效地异常处理能使程序更加健壮、易于调试。那么这篇文章总结了Java有效处理异常的三个原则,有需要的朋友们可以参考借鉴。
    2016-09-09
  • IDEA远程连接HBase及其Java API实战详解

    IDEA远程连接HBase及其Java API实战详解

    这篇文章主要介绍了IDEA远程连接HBase及其Java API实战详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • SpringBoot使用Redis实现消息队列的方法小结

    SpringBoot使用Redis实现消息队列的方法小结

    在应用中把Redis当成消息队列来使用已经屡见不鲜了,我想主要原因是当代应用十有八九都会用到 Redis,因此不用再引入其他消息队列系统,而且Redis提供了好几种实现消息队列的方法,用起来也简单,本文给大家介绍了SpringBoot使用Redis实现消息队列的方法小结
    2024-04-04

最新评论