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实现图像多分类
直接上效果图: 代码仓库和视频演示b站视频005期: 到此一游7758258的个人空间-到此一游7758258个人主页-哔哩哔哩视频 代码展示: 数据集在datasets文件夹下 运行01train.py即可训练 训练结束后会保存模型在本地 运行02pyqt.py会有一个可视化…...
slice() 方法,使用 concat() 方法, [...originalArray],find(filter),移出类名 removeAttr()
在JavaScript中,在 JavaScript 中,clone 不是一个原生的数组方法。但是你可以使用其他方法来实现克隆数组的功能。 以下是几种常见的克隆数组的方法: 使用 slice() 方法: const originalArray [1, 2, 3]; const clonedArray …...

Zabbix报警机制、配置钉钉机器人、自动发现、主动监控概述、配置主动监控、zabbix拓扑图、nginx监控实例
day02 day02配置告警用户数超过50,发送告警邮件实施验证告警配置配置钉钉机器人告警创建钉钉机器人编写脚本并测试添加报警媒介类型为用户添加报警媒介创建触发器创建动作验证自动发现配置自动发现主动监控配置web2使用主动监控修改配置文件,只使用主动…...

ELK日志分析系统概述及部署
ELK 平台是一套完整的日志集中处理解决方案,将 ElasticSearch、Logstash 和 Kibana 三个开源工具配合使用,完成更强大的用户对日志的查询、排序、统计需求。 一、ELK概述 1、组件说明 ①ElasticSearch ElasticSearch是基于Lucene(一个全文…...
HTML拖拽
拖拽的流程:鼠标按下(mousedown)→鼠标移动(mousemove)→鼠标松开(moveup) 需要理解的几个api: clientX/clientY: 相对于浏览器视窗内的位置坐标(不包括浏览器收藏夹和顶部网址部分)pageX/pageY: 该属性会考虑滚动,如…...

【vue】 vue2 监听滚动条滚动事件
代码 直接上代码,vue单文件 index.vue <template><div class"content" scroll"onScroll"><p>内容</p><p>内容</p><p>内容</p><p>内容</p><p>内容</p><p>内容…...
k8s目录
k8s笔记目录,更新中... 一 概念篇 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和…...

设计模式行为型——解释器模式
目录 什么是解释器模式 解释器模式的实现 解释器模式角色 解释器模式类图 解释器模式举例 解释器模式代码实现 解释器模式的特点 优点 缺点 使用场景 注意事项 实际应用 什么是解释器模式 解释器模式(Interpreter Pattern)属于行为型模式&…...
使用 Webpack 优化前端开发流程
在现代前端开发中,构建工具的选择和优化流程的设计至关重要。Webpack 是一个功能强大的前端构建工具,能够优化我们的开发流程,提高开发效率和项目性能。本文将介绍如何使用 Webpack 来优化前端开发流程。 代码优化和资源管理也是前端项目中不…...
mysql的分库分表脚本
目录 一.分库分表优点二.过程思路脚本实现验证 一.分库分表优点 1,提高系统的可扩展性和性能:通过分库分表,可以将数据分布在多个节点上,从而提高系统的负载能力和处理性能。 2,精确备份和恢复:分库分表备…...

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 服务端代码 简述
客户端代码和服务端代码是计算机网络交互中的两种重要代码类型。在计算机网络中,客户端和服务器是一对设备模型,客户端(Client)负责向服务器发送请求,服务器(Server)负责处理请求并返回给客户端…...
【娱乐圈明星知识图谱2】信息抽取
目录 1. 项目介绍 2. 信息抽取介绍 3. ChatGPT 信息抽取代码实战 4. 信息抽取主逻辑 5. 项目源码 1. 项目介绍 利用爬虫项目中爬取的大量信息 【娱乐圈明星知识图谱1】百科爬虫_Encarta1993的博客-CSDN博客娱乐圈明星知识图谱百度百科爬虫百度百科爬虫百度百科爬虫百度百…...
C++ rand的用法
C rand的用法 rand()介绍srand()介绍产生随机数的用法产生一定范围随机数的通用表示公式 我们知道 rand() 函数可以用来产生随机数,但是这不是真正意义上的随机数,是一个伪随机数,是根据一个数(我们可以称它为种子)为基…...

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

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

面试必考精华版Leetcode2130.链表最大孪生和
题目: 代码(首刷看解析 day22): 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抓包 | 竟然有这些骚操作,太神奇了? Fiddler响应拦截数据篡改,实现特殊场景深度测试(一) 利用Fiddler抓包调试工具,实现mock数据特殊场景深度测试(二) 利用Fiddler抓包调试工…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...

label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...

解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...