当前位置: 首页 > news >正文

SpringApplication对象的构建及spring.factories的加载时机

构建SpringApplication对象源码:
1、调用启动类的main()方法,该方法中调用SpringApplication的run方法。

@SpringBootApplication
public class SpringbootdemoApplication {public static void main(String[] args) {SpringApplication.run(SpringbootdemoApplication.class, args);}
}

2、调用SpringApplication的run()方法的重载方法,在发方法内构建了SpringApplication对象

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {return new SpringApplication(primarySources).run(args);}

3、构建SpringApplication对象。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {//resourceLoader为nullthis.resourceLoader = resourceLoader;//PrimarySources(即启动类)一定不能为nullAssert.notNull(primarySources, "PrimarySources must not be null");//初始化primarySources属性this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));//从Classpath中推断Web应用类型。this.webApplicationType = WebApplicationType.deduceFromClasspath();this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();}

构建过程:

接下来我们来详细研究一下上述过程。在构建SpringApplication对象过程中,此时resourceLoader
为null,primarySources一定不为空且需要初始化为启动类SpringbootdemoApplication
。从Classpath中推断出Web应用类型并初始化webApplicationType,通过getSpringFactoriesInstances()获取Spring工厂实例来初始化bootstrapRegistryInitializers,initializers(List<ApplicationContextInitializer<?>>应用程序上下文初始化器列表), listeners(List

4、从Classpath推断Web应用类型,调用的是WebApplicationType类的deduceFromClasspath()方法。如果Classpath中包含DispatcherHandler,则表明当前WebApplicationType为REACTIVE;如果既不包含javax.servlet.Servlet也不包含Spring中的ConfigurableWebApplicationContext,则表明当前WebApplicationType为NONE; 上述两种都不是则表明当前WebApplicationType是SERVLET

static WebApplicationType deduceFromClasspath() {// 如果org.springframework.web.reactive.DispatcherHandler的class文件存在且可以加载//不存在org.springframework.web.servlet.DispatcherServlet//不存在org.glassfish.jersey.servlet.ServletContainer//则返回REACTIVE表明 该应用程序应作为响应式Web应用程序运行,并应启动嵌入式servlet Web 服务器if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {return WebApplicationType.REACTIVE;}//遍历SERVLET 指标类//如果不存在javax.servlet.Servlet也不存在org.springframework.web.context.ConfigurableWebApplicationContext//则返回NONE表明该应用程序不应作为 Web 应用程序运行,也不应启动嵌入式 Web 服务器for (String className : SERVLET_INDICATOR_CLASSES) {if (!ClassUtils.isPresent(className, null)) {return WebApplicationType.NONE;}}//返回SERVLET表明该应用程序应作为基于servlet的 Web 应用程序运行,并应启动嵌入式 servlet Web 服务器。return WebApplicationType.SERVLET;}

spring.factories的加载时机
1、在构建SpringApplication中,初始化其属性bootstrapRegistryInitializers属性时进行加载 /META-INF/spring.factories。
2、构建SpringApplication对象时,通过调用getSpringFactoriesInstances(Class type)获取工厂实例。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {......this.webApplicationType = WebApplicationType.deduceFromClasspath();this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));......}

3、我们以初始化bootstrapRegistryInitializers为例讲解,getSpringFactoriesInstances(Class type, Class<?>[] parameterTypes, Object… args)中首先获取ClassLoader ,通过SpringFactoriesLoader机制,根据ClassLoader从类路径jar包中加载META-INF/spring.factories下的所有工厂类及其实现类 。

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) {//获取ClassLoader ClassLoader classLoader = getClassLoader();// 使用SpringFactoriesLoader机制加载出工厂名,并放入Set集合中确保其唯一性。但是在META-INF/spring.factories中并无BootstrapRegistryInitializer所以此处的names的size为0。Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));//创建Spring工厂实例(但因为上述names的size为0,所以对于BootstrapRegistryInitializer来说它并不会创建SpringFactory实例)List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);//通过AnnotationAwareOrderComparator对Spring工厂实例排序AnnotationAwareOrderComparator.sort(instances);return instances;}

4、SpringFactoriesLoader中的loadFactoryNames来加载META-INF/spring.factories下的所有工厂类及其实现类

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {//获取当前使用的ClassLoaderClassLoader classLoaderToUse = classLoader;if (classLoaderToUse == null) {//为空,则获取SpringFactoriesLoader的类加载器classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();}//org.springframework.boot.BootstrapRegistryInitializerString factoryTypeName = factoryType.getName();//加载META-INF/spring.factories下的扩展类,没有值的返回一个空列表。return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());}

5、重点关注SpringFactoriesLoader中的loadSpringFactories(ClassLoader classLoader),该方法具体实现了从META-INF/spring.factories下加载扩展类。

//工厂类所在位置,在多个jar文件中都有。
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {//检查缓存中是否有工厂类Map<String, List<String>> result = cache.get(classLoader);if (result != null) {return result;}//缓存中没有,初始化resultresult = new HashMap<>();try {//加载META-INF/spring.factories下的一系列元素Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);//迭代遍历while (urls.hasMoreElements()) {//spring-boot-版本号.jar文件中Spring.factories所在的绝对路径URL url = urls.nextElement();//构建UrlResourceUrlResource resource = new UrlResource(url);//加载该UrlResource中的属性(即Spring.factories中的键值对)Properties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry<?, ?> entry : properties.entrySet()) {//父类工厂类型名String factoryTypeName = ((String) entry.getKey()).trim();//子类工厂实现类名数组(在Spring.factories中多个用逗号分隔)String[] factoryImplementationNames =StringUtils.commaDelimitedListToStringArray((String) entry.getValue());//将工厂类型名,工厂实现类列表放入名为result的 Map<String, List<String>>中,key为工厂类型名,value为工厂实现类列表。for (String factoryImplementationName : factoryImplementationNames) {result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()).add(factoryImplementationName.trim());}}}//将result中的所有list都置为包含唯一元素的不可修改的listresult.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;}
bootstrapRegistryInitializers 的初始化中实现了加载META-INF/spring.factories中工厂扩展类(但是在META-INF/spring.factories并无bootstrapRegistryInitializers ),并将其放入缓存Map<String, List>,其中key为父类工厂名,value为其对应扩展类列表。之后initializers(ApplicationContextInitializer列表)以及listeners(ApplicationListener列表)的初始化都是从该缓存中获取值。

6、接下里我们看一下第三步中的createSpringFactoriesInstances()方法。由于bootstrapRegistryInitializers 在META-INF/spring.factories中并不存在。所以我们只有它返回的instances是空的即它不会创建SpringFactoriesInstances。但是初始化initializers,listeners时,却会。我们看一下具体源码。

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,ClassLoader classLoader, Object[] args, Set<String> names) {//初始化names.size()大小的list存放创建出来的实例。List<T> instances = new ArrayList<>(names.size());//遍历传入的工厂扩展类实例名for (String name : names) {try {//通过反射获取instance的Class对象Class<?> instanceClass = ClassUtils.forName(name, classLoader);//instanceClass是一个type类型,向下,否则抛异常IllegalArgumentExceptionAssert.isAssignable(type, instanceClass);//获取instanceClass的构造器Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);//通过BeanUtils的instantiateClass()方法实例化类。T instance = (T) BeanUtils.instantiateClass(constructor, args);//将实例化出来的类放入instancesinstances.add(instance);}catch (Throwable ex) {throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);}}return instances;}

相关文章:

SpringApplication对象的构建及spring.factories的加载时机

构建SpringApplication对象源码: 1、调用启动类的main()方法,该方法中调用SpringApplication的run方法。 SpringBootApplication public class SpringbootdemoApplication {public static void main(String[] args) {SpringApplication.run(SpringbootdemoApplication.class, …...

基于传统检测算法hog+svm实现图像多分类

直接上效果图&#xff1a; 代码仓库和视频演示b站视频005期&#xff1a; 到此一游7758258的个人空间-到此一游7758258个人主页-哔哩哔哩视频 代码展示&#xff1a; 数据集在datasets文件夹下 运行01train.py即可训练 训练结束后会保存模型在本地 运行02pyqt.py会有一个可视化…...

slice() 方法,使用 concat() 方法, [...originalArray],find(filter),移出类名 removeAttr()

在JavaScript中&#xff0c;在 JavaScript 中&#xff0c;clone 不是一个原生的数组方法。但是你可以使用其他方法来实现克隆数组的功能。 以下是几种常见的克隆数组的方法&#xff1a; 使用 slice() 方法&#xff1a; const originalArray [1, 2, 3]; const clonedArray …...

Zabbix报警机制、配置钉钉机器人、自动发现、主动监控概述、配置主动监控、zabbix拓扑图、nginx监控实例

day02 day02配置告警用户数超过50&#xff0c;发送告警邮件实施验证告警配置配置钉钉机器人告警创建钉钉机器人编写脚本并测试添加报警媒介类型为用户添加报警媒介创建触发器创建动作验证自动发现配置自动发现主动监控配置web2使用主动监控修改配置文件&#xff0c;只使用主动…...

ELK日志分析系统概述及部署

ELK 平台是一套完整的日志集中处理解决方案&#xff0c;将 ElasticSearch、Logstash 和 Kibana 三个开源工具配合使用&#xff0c;完成更强大的用户对日志的查询、排序、统计需求。 一、ELK概述 1、组件说明 ①ElasticSearch ElasticSearch是基于Lucene&#xff08;一个全文…...

HTML拖拽

拖拽的流程&#xff1a;鼠标按下(mousedown)→鼠标移动(mousemove)→鼠标松开(moveup) 需要理解的几个api&#xff1a; clientX/clientY: 相对于浏览器视窗内的位置坐标&#xff08;不包括浏览器收藏夹和顶部网址部分&#xff09;pageX/pageY: 该属性会考虑滚动&#xff0c;如…...

【vue】 vue2 监听滚动条滚动事件

代码 直接上代码&#xff0c;vue单文件 index.vue <template><div class"content" scroll"onScroll"><p>内容</p><p>内容</p><p>内容</p><p>内容</p><p>内容</p><p>内容…...

k8s目录

k8s笔记目录&#xff0c;更新中... 一 概念篇 1.1概念介绍 1.2 pod 1.3 controller 1.3.1 deployment 1.3.2 statefulset 1.3.3 daemonset 1.3.4 job和cronJob1 1.4 serivce和ingress 1.5 配置与存储 1.5.1 configMap 1.5.2 secret 1.5.3 持久化存储 1.5.4 pv和…...

设计模式行为型——解释器模式

目录 什么是解释器模式 解释器模式的实现 解释器模式角色 解释器模式类图 解释器模式举例 解释器模式代码实现 解释器模式的特点 优点 缺点 使用场景 注意事项 实际应用 什么是解释器模式 解释器模式&#xff08;Interpreter Pattern&#xff09;属于行为型模式&…...

使用 Webpack 优化前端开发流程

在现代前端开发中&#xff0c;构建工具的选择和优化流程的设计至关重要。Webpack 是一个功能强大的前端构建工具&#xff0c;能够优化我们的开发流程&#xff0c;提高开发效率和项目性能。本文将介绍如何使用 Webpack 来优化前端开发流程。 代码优化和资源管理也是前端项目中不…...

mysql的分库分表脚本

目录 一.分库分表优点二.过程思路脚本实现验证 一.分库分表优点 1&#xff0c;提高系统的可扩展性和性能&#xff1a;通过分库分表&#xff0c;可以将数据分布在多个节点上&#xff0c;从而提高系统的负载能力和处理性能。 2&#xff0c;精确备份和恢复&#xff1a;分库分表备…...

JavaEE初阶之文件操作 —— IO

目录 一、认识文件 1.1认识文件 1.2树型结构组织 和 目录 1.3文件路径(Path) 1.4其他知识 二、Java 中操作文件 2.1File 概述 2.2代码示例 三、文件内容的读写 —— 数据流 3.1InputStream 概述 ​3.2FileInputStream 概述 3.3代码示例 3.4利用 Scanner 进行字…...

客户端代码 VS 服务端代码 简述

客户端代码和服务端代码是计算机网络交互中的两种重要代码类型。在计算机网络中&#xff0c;客户端和服务器是一对设备模型&#xff0c;客户端&#xff08;Client&#xff09;负责向服务器发送请求&#xff0c;服务器&#xff08;Server&#xff09;负责处理请求并返回给客户端…...

【娱乐圈明星知识图谱2】信息抽取

目录 1. 项目介绍 2. 信息抽取介绍 3. ChatGPT 信息抽取代码实战 4. 信息抽取主逻辑 5. 项目源码 1. 项目介绍 利用爬虫项目中爬取的大量信息 【娱乐圈明星知识图谱1】百科爬虫_Encarta1993的博客-CSDN博客娱乐圈明星知识图谱百度百科爬虫百度百科爬虫百度百科爬虫百度百…...

C++ rand的用法

C rand的用法 rand()介绍srand()介绍产生随机数的用法产生一定范围随机数的通用表示公式 我们知道 rand() 函数可以用来产生随机数&#xff0c;但是这不是真正意义上的随机数&#xff0c;是一个伪随机数&#xff0c;是根据一个数&#xff08;我们可以称它为种子&#xff09;为基…...

element时间选择器的默认值

概览&#xff1a;vue使用element组件&#xff0c;需要给时间选择器设置默认值&#xff0c;场景一&#xff1a;默认时间选择器&#xff0c;场景二&#xff1a;时间范围选择器&#xff0c;开始时间和结束时间。 一、默认时间选择器 实现思路&#xff1a; element组件的v-model绑…...

fiddler过滤器

1、fiddler Fiddler是一个免费、强大、跨平台的HTTP抓包工具。下载地址 2、为什么适用过滤器 不适用过滤器时&#xff0c;所有的报文都会被抓包。 我们在开发或测试时&#xff0c;只需要抓包某个域名下的报文 &#xff0c;以“www.baidu.com”为例&#xff0c;不设置过滤器&…...

面试必考精华版Leetcode2130.链表最大孪生和

题目&#xff1a; 代码&#xff08;首刷看解析 day22&#xff09;&#xff1a; class Solution { public:int pairSum(ListNode* head) {ListNode* slowhead;ListNode* fasthead->next;while(fast->next!nullptr){slowslow->next;fastfast->next->next;}//反转…...

qemu kvm 新建虚拟机

开始菜单打开虚拟机管理器...

Charles抓包工具使用(一)(macOS)

Fiddler抓包 | 竟然有这些骚操作&#xff0c;太神奇了&#xff1f; Fiddler响应拦截数据篡改&#xff0c;实现特殊场景深度测试&#xff08;一&#xff09; 利用Fiddler抓包调试工具&#xff0c;实现mock数据特殊场景深度测试&#xff08;二&#xff09; 利用Fiddler抓包调试工…...

忍者像素绘卷效果展示:云端画布背景+金橙配色+浮雕UI真实渲染效果

忍者像素绘卷效果展示&#xff1a;云端画布背景金橙配色浮雕UI真实渲染效果 1. 视觉风格惊艳呈现 忍者像素绘卷带来了全新的视觉体验&#xff0c;将传统像素艺术与现代设计理念完美融合。这款基于Z-Image-Turbo深度优化的图像生成工具&#xff0c;创造了一个明亮通透的创作环…...

OpenClaw开源贡献指南:Qwen3.5-9B技能模块PR提交流程

OpenClaw开源贡献指南&#xff1a;Qwen3.5-9B技能模块PR提交流程 1. 为什么需要你的贡献 去年冬天&#xff0c;当我第一次尝试用OpenClaw自动整理电脑上的照片时&#xff0c;发现现有的技能库缺少一个"智能相册整理"模块。那一刻我突然意识到&#xff1a;这个开源项…...

VBA UserForm控件交互实战:跨窗体数据传递与动态更新

1. UserForm基础与跨窗体数据传递原理 刚接触VBA UserForm时&#xff0c;我经常被各种控件的交互问题困扰。特别是当需要多个窗体协同工作时&#xff0c;数据传递就成了大难题。记得有次做订单管理系统&#xff0c;主窗体收集客户信息&#xff0c;子窗体处理产品明细&#xff0…...

Qwen3-14B镜像部署指南:单卡RTX 4090D上快速启用中文大模型推理

Qwen3-14B镜像部署指南&#xff1a;单卡RTX 4090D上快速启用中文大模型推理 1. 镜像概述与核心优势 Qwen3-14B私有部署镜像是专为RTX 4090D显卡优化的中文大模型推理解决方案。这个镜像最大的特点就是"开箱即用"——所有环境依赖、模型权重、优化组件都已预装配置好…...

DanKoe 视频笔记:致富之路:三个关键决策

在本节课中&#xff0c;我们将要学习决定个人能否实现财富积累的三个核心决策。这些决策并非关于具体的赚钱技巧&#xff0c;而是关于如何从根本上重塑你的思维方式和行为模式&#xff0c;为创造财富铺平道路。 概述 许多人渴望财富&#xff0c;但往往不得其法。真正的致富之…...

让大模型乖乖听话:新手程序员必备的Prompt写作秘籍(收藏版)

本文探讨了如何通过精心设计的Prompt让大模型按照要求思考&#xff0c;提升任务执行的准确性。作者提出了一个有效的Prompt结构&#xff0c;包括角色/任务定义、核心原则、上下文处理、CoT(Chain of Thoughts)思考链、输出规范和Few-Shot示例等模块。文章还介绍了如何借助模型生…...

React Overdrive与Next.js集成:构建流畅页面过渡

React Overdrive与Next.js集成&#xff1a;构建流畅页面过渡 【免费下载链接】react-overdrive Super easy magic-move transitions for React apps 项目地址: https://gitcode.com/gh_mirrors/re/react-overdrive React Overdrive是一款为React应用提供超简单魔法移动过…...

Youtu-VL-4B-Instruct效果展示:高清OCR+精准Box定位+多轮图文对话作品集

Youtu-VL-4B-Instruct效果展示&#xff1a;高清OCR精准Box定位多轮图文对话作品集 1. 引言&#xff1a;当“看图说话”进化成“看图办事” 你有没有遇到过这种情况&#xff1f;看到一张复杂的图表&#xff0c;想快速提取里面的数据&#xff0c;却要手动一个个去数&#xff1b…...

珠海内有哪些做专精特新,创新型中小企业。权代理事务通过率高

在珠海&#xff0c;众多专精特新、创新型中小企业在发展过程中&#xff0c;知识产权代理事务变得尤为重要&#xff0c;而珠海飞拓知识产权代理事务凭借其独特优势&#xff0c;成为了高通过率的代表。企业痛点催生专业服务在专精特新、创新型中小企业培育与申报过程中&#xff0…...

OpenClaw学习助手:Gemma-3-12b-it生成错题本与定制复习计划

OpenClaw学习助手&#xff1a;Gemma-3-12b-it生成错题本与定制复习计划 1. 为什么需要AI学习助手&#xff1f; 作为一名经常需要处理大量学习资料的开发者&#xff0c;我一直在寻找能够提升学习效率的工具。传统的错题本整理方式需要手动抄写题目、标注知识点、寻找同类练习题…...