我们先贴源码:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {return getSpringFactoriesInstances(type, new Class<?>[] {});}private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {//获取当前的classloaderClassLoader classLoader = getClassLoader();// Use names and ensure unique to protect against duplicates// 使用class名称保证不重复,使用set结构保证不重复Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));// 开始创建实例List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);// 进行排序AnnotationAwareOrderComparator.sort(instances);return instances;}@SuppressWarnings("unchecked")//使用反射构造对象private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,ClassLoader classLoader, Object[] args, Set<String> names) {//创建spring工厂实例List<T> instances = new ArrayList<>(names.size());for (String name : names) {try {//静态初始化(用名称和类加载器去类路径上找类进行加载)Class<?> instanceClass = ClassUtils.forName(name, classLoader);//判断创建出来的类是否有继承关系Assert.isAssignable(type, instanceClass);//创建构造器Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);//创建真正的实例T instance = (T) BeanUtils.instantiateClass(constructor, args);instances.add(instance);}catch (Throwable ex) {throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);}}return instances;}

一共是三个方法:getSpringFactoriesInstances(有两个,有一个方法重载)、createSpringFactoriesInstances。一开始我还好奇,为什么new SprigApplication中可以使用接口进行实例化,后来深入代码才发现是自己的理解有一定偏差,请看SpringFactoriesLoader.loadFactoryNames(type, classLoader),具体代码如下。

 /*** Load the fully qualified class names of factory implementations of the* given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given* class loader.* <p>As of Spring Framework 5.3, if a particular implementation class name* is discovered more than once for the given factory type, duplicates will* be ignored.* @param factoryType the interface or abstract class representing the factory* @param classLoader the ClassLoader to use for loading resources; can be* {@code null} to use the default* @throws IllegalArgumentException if an error occurs while loading factory names* @see #loadFactories*/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());}

使用给定的类加载器,从 "META-INF/spring.factories "中加载给定类型的工厂实现的完全限定类名。也就是说看似传入到getSpringFactoriesInstances中的是一个接口class,但实际加载的类实例却是从META-INF/spring.factories中发现的,比如传入ApplicationContextInitializer.class加载的实际上是:

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

其中还有一点要注意,在loadFactoryNames中调用了loadSpringFactories中针对io操作:classLoader.getResources有一个缓存的小细节如下,注意是使用的类加载器进行的分类:

//这里除了第一次加载耗时,其他地方都是使用的缓存。
loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
//先查询缓存,加载所有的META-INF/spring.factories的全类名Map<String, List<String>> result = cache.get(classLoader);if (result != null) {return result;}//如果没有开始加载result = new HashMap<>();try {//加载了当前类加载器的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 = PropertiesLoaderUtils.loadProperties(resource);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;}

此时全部需要使用的,或者说是待使用的全类名就都加载到缓存中了。

【细读源码】SpringBoot初始化实例中的重要方法getSpringFactoriesInstances相关推荐

  1. springboot源码: springboot初始化过程

    1. new SpringApplication() 在springboot种执行这一行操作的时候,SpringApplication.run(DemoApplication.class, args) ...

  2. jQuery源码分析之实例find和filter方法的区别七问

    问题1:jQuery.filter的源码是什么? jQuery.filter = function( expr, elems, not ) {var elem = elems[ 0 ];//如果含有第 ...

  3. spring4.1.8初始化源码学习三部曲之三:AbstractApplicationContext.refresh方法

    本章是<spring4.1.8初始化源码学习三部曲>系列的终篇,重点是学习AbstractApplicationContext类的refresh()方法: 原文地址:https://blo ...

  4. 注册中心 Eureka 源码解析 —— 应用实例注册发现(五)之过期

    2019独角兽企业重金招聘Python工程师标准>>> 摘要: 原创出处 http://www.iocoder.cn/Eureka/instance-registry-evict/ ...

  5. 从源码角度解析Android中APK安装过程

    从源码角度解析Android中APK的安装过程 1. Android中APK简介 Android应用Apk的安装有如下四种方式: 1.1 系统应用安装 没有安装界面,在开机时自动完成 1.2 网络下载 ...

  6. 深入源码理解.NET Core中Startup的注册及运行

    开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程序的起点.通过使用Startup,可以配置化处理所有向应用程序所做的请求的管道,同时也可 ...

  7. 源码里没有configure_深入源码理解.NET Core中Startup的注册及运行

    开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程序的起点.通过使用Startup,可以配置化处理所有向应用程序所做的请求的管道,同时也可 ...

  8. [文档+源码]SpringBoot+Mysql实现的宠物在线商城宠物交易平台宠物店源码

      博主介绍:✌在职Java研发工程师.专注于程序设计.源码分享.技术交流.专注于Java技术领域和毕业设计✌ 项目名称 [文档+源码]SpringBoot+Mysql实现的宠物在线商城宠物交易平台宠 ...

  9. RTKLIB源码——如何在VS2019中配置、调试

    RTKLIB源码--如何在VS2019中配置.调试 一. 准备源码: 二.Visual Studio中新建工程: 三.编译结果: 四.实例 一. 准备源码: 注:第三方rtklib修改后的源码地址 h ...

最新文章

  1. linux做网卡bond,linux下设置网卡bond
  2. python 中if __name__ = '__main__' 的作用
  3. python.freelycode.com-优化Pandas代码执行速度入门指南
  4. 代码提示_PHPStorm 支持 Laravel Facades 的代码提示
  5. CSS3 Filter的十种特效
  6. php二维数组排序 按照指定的key 对数组进行排序
  7. docker build -t_在Docker环境构建、打包和运行Spring Boot应用
  8. Serverless实战之路
  9. python推荐系统设置_用Python构建你自己的推荐系统
  10. oracle正则表达式
  11. ffmpeg(5):SDL相关学习
  12. 整理Oracle日期时间函数
  13. 大文件怎样实现快速上传?
  14. CSS 3 五光十色的变色龙动画的制作
  15. ORVIBO 精灵款升级分析
  16. 数据库驱动和maven
  17. STM32F103(1)
  18. 相对定位的元素会在原先的地方
  19. 2009.01.19(山寨)
  20. 零代码搭建一个温度传感器数据采集与显示软件

热门文章

  1. debian+linux百度云,Linux Centos/Ubuntu/Debian系统图形化界面挂BT、PT一键包(rtorrent+rutorrent)...
  2. Python的下标如何获取
  3. 【程序员2公务员】关于体制调研的一些常见问题总结
  4. Whale帷幄 - 餐饮业数字化转型的解决方案 餐饮智慧门店的营销策略
  5. 【EPLAN 公网部件库搭建与使用】
  6. 【bzoj 4202】石子游戏(博弈论+LCT)
  7. python qq api_基于Python的QQ号码测吉凶api调用代码实例
  8. PCB阻焊层太近了会不会有问题?
  9. 小儿机器人编程基础课
  10. 国网刘冬梅:信息系统灾难的超前防范与安全预警