死磕Spring,什么是SPI机制,对SpringBoot自动装配有什么帮助
文章目录
- 如果没时间看的话,在这里直接看总结
- 一、Java SPI的概念和术语
- 二、看看Java SPI是如何诞生的
- 三、Java SPI应该如何应用
- 四、从0开始,手撸一个SPI的应用实例
- 五、SpringBoot自动装配
- 六、Spring SPI机制与Spring Factories机制做对比
- 七、这里是给我自己提个醒
如果没时间看的话,在这里直接看总结
1. SPI是一个机制,流程由三个组件构成
- ServiceLoader,就是ClassLoader;
- Service,是接口,作为文件(在META-INF/services目录下)的名称
- ServiceProvider,是接口的实现类,作为文件(在META-INF/services目录下)的内容
2. SPI执行流程
- ServiceLoader通过classpath路径,加载指定的Service文件,然后使用里面合适的内容ServiceProvider
一、Java SPI的概念和术语
SPI(Service Provider Interface):基于ClassLoader,发现并加载服务,机制
SPI由三个组件构成:Service、Service Provider、ServiceLoader
- Service:是一个公开的接口或抽象类,定义了一个抽象的功能模块(文件名称)
- Service Provider:是Service的实现类(文件内容)
- ServiceLoader:是SPI机制中的核心组件,负责在运行时发现并加载Service Provider
二、看看Java SPI是如何诞生的
-
在Java SPI出现之前,Class.forName()要自己根据需求写驱动类

-
JDBC要求Driver实现类在类加载的时候,能将自身的实例对象自动注册到DriverManager中,从而加载数据库驱动。

-
Java SPI逐渐融入JDBC







三、Java SPI应该如何应用
- 规范的配置文件


- Service Provider类必须具备无参的默认构造方法

在JDBC中的对应实现

- 保证能加载到配置文件和Service Provider类

在JDBC中的对应实现

总结:上述除了导包需要自己动手以外,其他的手续都是导包之后,Java SPI自动完成的
四、从0开始,手撸一个SPI的应用实例










总体流程

五、SpringBoot自动装配
参考视频:每一帧都是干货!15分钟的视频花2小时看
参考文章:springboot自动装配到底是什么意思?
参考文章:建立META-INF/spring.factories文件的意义何在
参考文章:springboot自动装配原理-以redis为例
参考文章:聊聊 SpringBoot 自动装配原理
参考文章:spring.factories 文件的位置
1. 手动装配Redis实例
- 加入pom依赖
<dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-redis</artifactId><version>2.0.9.RELEASE</version>
</dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.9.0</version>
</dependency>
- 配置xml的bean的配置
//配置连接池<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"><property name="minIdle" value="10"></property><property name="maxTotal" value="20"></property></bean>//配置连接工厂<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"><property name="hostName" value="47.104.128.12"></property><property name="password" value="123456"></property><property name="database" value="0"></property><property name="poolConfig" ref="poolConfig"></property></bean>//配置 redisTemplate 模版类<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"><property name="connectionFactory" ref="jedisConnectionFactory"/><!--如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to String!! --><property name="keySerializer"><bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/></property><property name="valueSerializer"><bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/></property><property name="hashKeySerializer"><bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/></property><property name="hashValueSerializer"><bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/></property></bean>
- 导入配置
@ImportResource(locations = “classpath:beans.xml”) 可以导入xml的配置文件
2. SpringBoot自动配置Redis实例
- 引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 配置Redis服务器
spring:redis:database:0host:127.0.0.1port:6379password:123456
- 直接使用RedisTemplate或StringRedisTemplate
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
- 提出问题:自动配置
- 我们除了通过maven引入一个starter外,其他什么也没有做,但是呢,SpringBoot就自动完成了Redis的配置,将相关的Bean对象注册到IOC容器中了。那么SpringBoot是如何做到这一点的呢?这就是这篇博客所要说明的问题了。
2. 自动配置,一切从注解@SpringBootApplicaiton说起
- @SpringBootApplication注解

- 下面我们逐步分析@EnableAutoConfiguration的自动配置
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {};
}
AutoConfigurationImportSelector.class的selectImports方法
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata){if(!isEnabled(annotationMetadata))return NO_IMPORTS;AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);//SpringBoot自动配置的入口方法AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationErtadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
- selectImports()方法中引用的getAutoConfigurationEntry()
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata){//1. 获取annotationMetadata的注解@EnableAutoConfiguration的属性AnnotationAttributes attributes = getAttributes(annotationMetadata);//2. 从资源文件Spring.factories中获取EnableAutoConfiguration对应的所有的类List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);//3. 通过在注解@EnableAutoConfiguration设置exclude的相关属性,可以排除指定的自动配置类Set<String> exclusions = getExclusions(anntationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);//4. 根据注解@Conditional来判断是否需要排除某些自动配置类configurations filter = filter(configurations, autoConfigurationMetadata);//5. 触发AutoConfiguration导入的相关事件fireAutoCOnfigurationImportEvents(configurations, exclusions);return new AutofigurationEntry(configurations, exclusions);
}
- getAutoConfigurationEntry()引用的getCandidateConfigurations()
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes){//通过SpringFactories机制,从配置文件Spring.factories中找出所有的自动配置类List<String> configurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, getBeanClassLoader());Assert.notEmpty(configurations,"No auto configuration classes found");return configurations;
}
SpringFactoriesLoader.loadFactoryNames方法调用loadSpringFactories方法从所有的jar包中读取META-INF/spring.factories文件信息。
// 参数:// Class<?> factoryType:需要被加载的工厂类的class// ClassLoader classLoader:类加载器public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {ClassLoader classLoaderToUse = classLoader;if (classLoaderToUse == null) {// 若没传入类加载器,使用该本类的类加载器classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();}// class.getName():获取该类的全类限定名字String factoryTypeName = factoryType.getName();// loadSpringFactories(classLoaderToUse) 返回是Map// Map.getOrDefault(A,B): A为Key,从Map中获取Value,若Value为Null,则返回B 当作返回值return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());}
loadSpringFactories()方法调用ClassLoader.getSystemResources()获取META-INF/spring.factories文件
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {MultiValueMap result = (MultiValueMap)cache.get(classLoader);if(result != null) {return result;} else {try {Enumeration ex = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");LinkedMultiValueMap result1 = new LinkedMultiValueMap();while(ex.hasMoreElements()) {URL url = (URL)ex.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);Iterator var6 = properties.entrySet().iterator();while(var6.hasNext()) {Entry entry = (Entry)var6.next();List factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));result1.addAll((String)entry.getKey(), factoryClassNames);}}cache.put(classLoader, result1);return result1;} catch (IOException var9) {throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9);}}}
下面是spring-boot-autoconfigure这个jar中spring.factories文件部分内容,选择带有EnableAutoConfiguration自动配置类。
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
六、Spring SPI机制与Spring Factories机制做对比
- 联系:Spring Factories自动装配借用了SPI机制,SPI机制本身就是一种思想,不是特定的技术。
- 区别:如下

七、这里是给我自己提个醒
META-IF/spring.factories是在Maven引入的Jar包中,每一个Jar都有自己META-IF/spring.factories,所以SpringBoot是去每一个Jar包里面寻找META-IF/spring.factories,而不是我的项目中存在META-IF/spring.factories(当然也可以存在,但是我项目的META-IF/spring.factories肯定没有类似以下这些东西)

相关文章:
死磕Spring,什么是SPI机制,对SpringBoot自动装配有什么帮助
文章目录如果没时间看的话,在这里直接看总结一、Java SPI的概念和术语二、看看Java SPI是如何诞生的三、Java SPI应该如何应用四、从0开始,手撸一个SPI的应用实例五、SpringBoot自动装配六、Spring SPI机制与Spring Factories机制做对比七、这里是给我自…...
因果推断10--一种大规模预算约束因果森林算法(LBCF)
论文:A large Budget-Constrained Causal Forest Algorithm 论文:http://export.arxiv.org/pdf/2201.12585v2.pdf 目录 0 摘要 1 介绍 2 问题的制定 3策略评价 4 方法 4.1现有方法的局限性。 4.2提出的LBCF算法 5验证 5.1合成数据 5.2离线生…...
Linux基础命令-df显示磁盘的使用情况
文章目录 文章目录 df 命令介绍 语法格式 基本参数 参考实例 1)以人类可读形式显示磁盘空间的使用情况 2)显示磁盘的inode信息 3)显示磁盘和文件系统类型 4)指定显示文件系统 5)显示所有磁盘空间中的内容 …...
如何使用goquery进行HTML解析以及它的源码分析和实现原理
目录 goquery 是什么 goquery 能用来干什么 goquery quick start 玩转goquery.Find() 查找多个标签 Id 选择器 Class 选择器 属性选择器 子节点选择器 内容过滤器 goquery 源码分析 图解源码 总结 goquery 简介 goquery是一款基于Go语言的HTML解析库,…...
【Java 数组和集合 区别及使用案例】
Java中数组和集合都是用来存储一组数据的容器,但是在实际使用中,它们有一些区别和不同的使用场景。 数组 vs 集合:存储方式 数组是一个固定长度的容器,它的长度一旦被初始化之后,就无法再改变了。而集合是一个动态长…...
使用pynimate制作动态排序图
大家好,数据可视化动画使用Python包就可以完成,效果如下:想要使用Pynimate,直接import一下就行:import pynimate as nim输入数据后,Pynimate将使用函数Barplot()来创建条形数据动画。…...
Mysql 事务的隔离性(隔离级别)
Mysql 中的事务分为手动提交和自动提交,默认是自动提交,所以我们在Mysql每输入一条语句,其实就会被封装成一个事务提交给Mysql服务端。 手动提交需要先输入begin,表示要开始处理事务,然后就是常见的sql语句操作了&…...
2023年网络安全竞赛——Python渗透测试PortScan.py
端口扫描Python渗透测试:需求环境可私信博主获取 任务环境说明: 服务器场景:PYsystem0041服务器场景操作系统:未知服务器场景FTP用户名:anonymous 密码:空1. 从靶机服务器的FTP上下载PortScan.py,编辑Python程序PortScan.py,实现...
【数据结构】栈的接口实现(附图解和源码)
栈的接口实现(附图解和源码) 文章目录栈的接口实现(附图解和源码)前言一、定义结构体二、接口实现(附图解源码)1.初始化栈2.销毁栈3.入栈4.判断栈是否为空5.出栈6.获取栈顶元素7.获取栈中元素个数三、源代码…...
LC-1255. 得分最高的单词集合(回溯)
1255. 得分最高的单词集合 难度困难60 你将会得到一份单词表 words,一个字母表 letters (可能会有重复字母),以及每个字母对应的得分情况表 score。 请你帮忙计算玩家在单词拼写游戏中所能获得的「最高得分」:能够由…...
从中国文化看面试挑人标准
文章目录标准一、面相1. 1 四白眼1.2 浓眉二、讲话2.1 言多与气虚总结本文结合中国面相,是个概率性问题,对于个体无效。 标准 正直,三观正,沟通好,技术。从概率上讲: 正直且三观正的人----有恒心&#x…...
谦卑对象设计模式
谦卑设计模式介绍 “谦卑”在这里是拟人化的,指难以测试的对象清晰地认识到自己的局限性,只发挥自己的桥梁和通信作用,并不从中干预信息的传输。 谦卑对象模式‘最初的设计目的是帮助单元测试的编写者区分容易测试的行为与难以测试的行为,并将它们隔离。其设计思路…...
QML Animation动画详解
1.Animation简介 Animation类型提供了四个属性: alwaysRunToEnd:该属性接收布尔类型的参数。该属性保存动画是否运行到完成才停止。当loops属性被设置时,这个属性是最有用的,因为动画将正常播放结束,但不会重新启动。…...
C#开发的OpenRA的加载界面边框的细节
C#开发的OpenRA的加载界面边框的细节 在前面已经看到加载整个界面, 如果仔细地看,会发现加载界面的边框有一个红色的框。 这个红色的边框到底是怎么样来的呢? 其实它不是实时画上去的,而从纹理贴图里贴上去的。 也许有一些人会问,纹理贴图里的图片这么小,怎么样会有这么大…...
计算机网络笔记、面试八股(四)—— TCP连接
本章目录4. TCP连接4.1 TCP报文段的首部格式4.2 TCP连接如何保证可靠4.3 ARQ协议4.3.1 停止等待ARQ协议4.3.1.1 无差错情况4.3.1.2 出现差错情况4.3.1.3 确认丢失和确认迟到4.3.2 连续ARQ协议4.3.2.1 流水线传输4.3.2.2 累积确认4.3.2.3 滑动窗口协议4.3.3 停止等待ARQ和连续AR…...
Centos7 安装jenkins java1.8版本
1. 首先安装好jdk1.8 2. 安装jenkins 命令:(可以在根目录,创建文件夹 mkdir home 然后在此文件夹下操作 cd /home) a 清华源,获取jenkins安装包 wget https://mirrors.tuna.tsinghua.edu.cn/jenkins/redhat/jenkins-2.346-1.1.noarch.rp…...
【每日阅读】JS知识(三)
var声明提升 js是一个解释性语言类型,预解析就是在执行代码之前对代码进行通读 var关键字是,在内存中声明一个变量名 js在代码执行之前 会经历两个环节 解释代码 和执行代码 声明式函数 内存中 先声明一个变量名是函数 这个名代表的是函数 乘法表 // for…...
Vue(6)
文章目录1. 自定义指令1.1 函数式1.2 对象式1.3 自定义指令常见坑1.4 创建全局指令2. 生命周期2.1 引出生命周期2.2 分析生命周期2.3 总结3. 组件3.1 认识组件3.2 使用组件 (非单文件组件)3.3 全局组件3.4 组件的几个注意点3.5 组件的嵌套3.6 VueComponent 构造函数3.7 一个重要…...
Neo4j列表函数
使用列表 标量列表函数 size() 函数返回列表中的元素的数量 MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WITH p, collect (m.title) AS MovieTitles WITH p, MovieTitles, size(MovieTitles) AS NumMovies WHERE NumMovies > 20 RETURN p.name AS Actor, NumMovies, Movie…...
55. 跳跃游戏
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标。示例 1:输入:nums [2,3,1,1,4]输出:true解释:可以先跳 1 步&#…...
ZPL文件操作避坑指南:从OPEN到CLOSE的5个常见错误排查
ZPL文件操作避坑指南:从OPEN到CLOSE的5个常见错误排查 在光学设计领域,ZPL宏作为ZEMAX的自动化利器,文件操作是绕不开的核心技能。但当你从教程里的完美示例转向真实项目时,那些被刻意简化的场景往往会暴露出各种"坑"。…...
避坑指南:解决Livox Mid-360双雷达点云融合时坐标系错乱与IMU数据混杂问题
Livox Mid-360双雷达点云融合实战:坐标系校准与IMU数据分离全解析 当你在RViz中看到两个Livox Mid-360雷达的点云像醉酒的水母一样随机飘动,而IMU数据又像被搅拌机混合过的果汁——恭喜你,遇到了多传感器融合的经典难题。这不是简单的参数调整…...
新手别慌!手把手教你用嘉立创EDA专业版搞定蓝桥杯平衡车PCB布局布线
从零到精通:嘉立创EDA专业版实战蓝桥杯平衡车PCB设计全攻略 第一次接触蓝桥杯电子设计竞赛的平衡车项目时,面对密密麻麻的元器件和错综复杂的布线要求,很多同学都会感到无从下手。本文将带你一步步攻克这个看似复杂的PCB设计任务,…...
5G核心网UDR深度解析:从签约数据管理到策略数据存储的完整流程
5G核心网UDR深度解析:从签约数据管理到策略数据存储的完整流程 在5G核心网(5GC)架构中,统一数据仓储功能(UDR)扮演着数据中枢的角色。作为电信级网络的关键组件,UDR不仅需要处理海量用户数据的实…...
一文搞懂Agent三大核心技术:Function Calling、MCP、A2A,小白也能轻松收藏学习!
本文详细解析了AI Agent的三大核心技术:Function Calling、MCP和A2A。Function Calling使AI能够主动获取外部信息,MCP为工具接入提供了标准化接口,而A2A则实现了多智能体之间的协作。通过这三个技术的演进,AI Agent的能力从点对点…...
M5Stack U126 RTC驱动库:PCF8563T嵌入式实时时钟深度解析
1. 项目概述M5Unit-RTC 是专为 M5Stack 生态中 Unit 系列模块设计的轻量级实时时钟(RTC)驱动库,对应硬件型号为U126—— 一款基于Ricoh RP5C01A 兼容架构、实际采用 NXP PCF8563T 实时时钟芯片的 IC 接口 RTC 模块。该模块集成高精度温度补偿…...
PhysX帧分配器:一帧一擦的高效艺术
写满就擦,擦完再写,永不停歇引子:数学老师的白板 还记得高中数学课吗? 老师走进教室,面前是一块干干净净的白板。他开始讲解——写公式、画图形、列步骤,白板渐渐被填满。下课铃响,老师拿起板擦…...
当multisim遇见ai助手:快马平台如何智能分析与优化你的电路设计
作为一名电子设计爱好者,最近在InsCode(快马)平台尝试了一个特别有意思的项目——用AI辅助优化Multisim电路设计。整个过程就像有个专业的电子工程师在旁边实时指导,分享下我的实践心得: 直流工作点智能诊断 输入一个简单的晶体管放大电路后&…...
Mojo+Python混合部署案例深度拆解(从Jupyter到生产环境的无缝迁移全路径)
第一章:MojoPython混合部署案例深度拆解(从Jupyter到生产环境的无缝迁移全路径)Mojo 作为新兴的系统级编程语言,与 Python 生态天然兼容,为机器学习模型从探索性开发(Jupyter Notebook)迈向高吞…...
K230 vs树莓派视觉套件:300元预算该选谁?实测对比工业检测场景
K230与树莓派视觉套件:300元预算下的工业检测实战对比 在工业自动化浪潮中,视觉检测系统正从大型企业向中小型制造车间快速渗透。当预算被严格限制在300元区间时,K230开发板与树莓派摄像头组合成为最受关注的两种解决方案。我们历时三个月在6…...

