聊聊JAVA的SPI机制

面试官:谢飞机,聊聊 Java SPI 机制? SPI(Service Provider Interface)是Java提供的一种服务发现机制。它允许在运行时动态地为某个接口查找和加载其具体的实现类,从而实现模块间的解耦。这是一种“面向接口编程”思想的经典实践。核心参与者服务定义方:通常是框架或平台的开发者,他们定义接口或抽象类。服务实现方:第三方开发者或厂商,他们根据接口规范提供具体的实现。服务使用方:应用程序,它只依赖于接口,通过SPI机制在运行时获取并调用具体的实现。 阅读详情

  一、SPI简介    

SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制。SPI常用于框架的功能拓展,最典型的应用就是Spring对数据库驱动的加载、SpringBoot自动配置的实现以及Dubbo插件可拔插实现。

        SPI说到底还是接口,但是与传统意义上的接口由有所区别。传统的接口由服务提供者制定并实现接口,供消费者调用,而SPI是自己制定接口自己消费服务,而实现则交给第三方。SPI有点像发布需求招标的过程,我把需求规格、标准发布出来,大家带着实现方案来投标,谁的方案好我就用谁的。

二、原生SPI开发流程

     

三、SPI的实现案例

项目的结构如下图,是一个Nacos为注册中心的微服务Demo项目,大家测试不需要这么复杂的项目,我这里是为了探索SpringBoot自动装配SPI顺手用了以前的微服务Demo。

common模块就包含很简单一个接口,这个模块会被接口实现方和调用方引入。

package com.wen;

public interface DbType {
    String getName();
}

MysqlSPI模块仅包含DbType接口的实现

package com.wen;


public class MysqlDbType implements DbType {
    @Override
    public String getName() {
        return "Mysql";
    }
}
discovery-client模块需要引入common模块和MysqlSPI,以及配置SPI的映射文件,映射文件名放在resources/META-INF/services下,文件内容就是MysqlSPI中DbTy实现类的全限定名

测试类

import com.wen.DbType;
import com.wen.client.NacosClientApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ServiceLoader;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = NacosClientApplication.class)
public class Example {


    @Test
    public void example01() {
        ServiceLoader<DbType> serviceLoader = ServiceLoader.load(DbType.class);
        System.out.println("JDK 原生SPI,会缓存实例到Map中");
        for (DbType search : serviceLoader) {
           System.out.println(search.getName());
     }
}

测试结果如下,我们成功拿到了,接口的实例,并执行了方法

四、SPI的实现源码探索

我们就从测试类中使用的ServiceLoader入手。ServiceLoader实现了Iterable,并且声明一个LazyIterator类型的变量lookupIterator,LazyIterator类是ServiceLoader的一个内部类,看名字就不难猜到他也实现了迭代器接口,不过它实现的是Iteratorj接口。注意图中的常量PREFIX,这就是为啥映射要配在这里的原因。下面还有的Map是为了缓存实例。

从ServiceLoader的load方法开始,不太重要的过程我们直接跳过。load走完其实啥也没干,仅仅就完成了ServiceLoader类的初始化。真正的接口实现的实例化是在迭代器遍历的过程中完成的。

我们对load的结果遍历,调用ServiceLoader的iterator方法生成一个迭代器实例,遍历过程会调用该迭代器的hasNext和Next方法,这里都是先从缓存读取,没取到就利用lookupIterator这个迭代器,临时去查找接口实现,并实例化。

LazyIterator的hasNext和next方法最后又调用了他的实例方法hasNextService和nextService

这里要重点关注了,真正干活就在这里

这个service是啥,前面忘了介绍没关系,就是他,也就是接口类的实例。

前面已经拿到了实现类的全限定名,这里最后一步,利用JAVA的反射机制进行实例化,并缓存。

到这里整个过程应该是相当清楚了。这里原生SPI的缺点也是很明显的,每定义一个接口我必须新增一个映射配置文件,另外如果一个接口有多个实现,我无法直接拿到我想要的那个,需要去循环。

五、Spring SPI实现

代码上有一点点改变,映射文件统一为spring.factories,文件内容是键值对,前面是接口全限定名,后面是接口实现的全限定名,多个实现以“,”分隔,多个接口换行符分隔。

测试代码需要调整一下,主要干活的类是SpringFactoriesLoader类

import com.wen.DbType;
import com.wen.client.NacosClientApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = NacosClientApplication.class)
public class Example {


    @Test
    public void example01() {
       List<DbType> dbTypeList= SpringFactoriesLoader.loadFactories(DbType.class, null);
        System.out.println("Spring SPI,非自动配置方式,实例不会缓存,更不会托管给Spring容器");
        for (DbType dbType:dbTypeList){
            System.out.println(dbType.getName());
        }
    }
}

看看测试结果

代码逻辑很清晰明了

我们先看下结果,缺点很明显,实例没有缓存,每次调用都要重新解析,实例化,完全没有体现出Spring应有的优势。

六、Spring SPI自动装配实现

前面我们介绍了Spring的SPI很朴素的实现方式,确实有些鸡肋。那么我们更进一步,试试自动配置。项目代码小调整一下,MysqlSPI下新加一个自动配置类

package com.wen;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;


@Configuration
public class DbTypeAutoConfiguration {
    @Bean(name = "mysql")
    @Primary
    public DbType dbType(){
        return () -> "Mysql";
    }

    @Bean(name = "mongodb")
    public DbType mongodb(){
        return () -> "Mongodb";
    }
}

映射配置文件需要调整一下,key修改成org.springframework.boot.autoconfigure.EnableAutoConfiguration

值改为刚刚修改的自动配置类的全限定名  com.wen.DbTypeAutoConfiguration

测试类变得更简单了,用例通过,说明自动装配是成功了。这才是Spring该有的样子。

七、既然到SPI的自动配置方式,SpringBoot的自动装配也简单聊下。

1、核心配置

@EnableAutoConfiguration 这是实现自动配置关键注解,通常在启动类要开启,因为启动类一般添加了@SpringBootApplication配置,而@SpringBootApplication注解实现使用了元注解@EnableAutoConfiguration,所以这两个开关并不需要我们手动打开。这两个注解只是开关,实际上干活的缺失AutoConfigurationImportSelector这个类,这个类主要负责处理条件注入、去重、过滤以及被注入类的搜索定位等前置工作,搜索定位还是依靠前面提到过SpringFactoriesLoader类

2、注入过程。我们主要关注三个问题点,入口在哪,如何定位目标,何时初始化

1)入口:依然是我们的启动类,最终到AbstractApplicationContext类的refresh方法,跟过代码就会知道,这里是Spring容器初始化组件的最核心部分。

2)、如何定位目标:定位spring.factories目标,下面是定位SPI映射文件中自动配置类途径的类和方法,过程有点长,不上图了。调用链路在下面表格顺序从上至下。

类名方法
AbstractApplicationContextpublic void refresh()
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory)
PostProcessorRegistrationDelegate
public static void invokeBeanFactoryPostProcessors(
       ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors)
ConfigurationClassPostProcessor
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) 
ConfigurationClassParser
public void parse(Set<BeanDefinitionHolder> configCandidates)
private void processDeferredImportSelectors() 

内部类DeferredImportSelectorGrouping
public Iterable<Group.Entry> getImports()
public void process(AnnotationMetadata annotationMetadata,
       DeferredImportSelector deferredImportSelector)
AutoConfigurationImportSelector
public void process(AnnotationMetadata annotationMetadata,
       DeferredImportSelector deferredImportSelector) 
public String[] selectImports(AnnotationMetadata annotationMetadata)
SpringFactoriesLoader
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) 

3)、何时初始化:上一步只是解析并缓存了各种BeanDefinition文件,真正实例化是AbstractApplicationContext的finishBeanFactoryInitialization方法

接着调用DefaultListableBeanFactory的preInstantiateSingletons方法,后面的过程就很简单了。

好了,篇幅有点长,可能略显啰嗦,但是本着把细节讲清楚为目标,我也就不嫌麻烦了,希望大家能有所收获。
关于MOS管栅极和栅介质(氧化物)的材料选择 大概解释了高K材料作为栅介质的好处,以及为什么会带来好处,同时引出了金属栅的优点,总结了使用高K材料和金属栅的原因。 阅读详情

相关推荐

02.1 知识图谱工具 Protege的下载安装与使用

Protege的下载安装与使用 本文是王昊奋老师《知识图谱》系列笔记第2.1篇。 文章目录Protege的下载安装与使用0 相关介绍0.0 Protege相关介绍0.1 Protégé的特点0.2 Protégé的用途1 Protege的下载1.1 准备工作:安装配置JAVA环境1.2 官网下载2 Protege的安装2.1 直接解压缩2.2 双击运行3 Protege的使用实例-创建类...

和而不流 7万+

Java——SPI机制详解

SPI 全称是 Service Provider Interface,是一种 JDK 内置的动态加载实现扩展点的机制,通过 SPI 技术我们可以动态获取接口的实现类,不用自己来创建。这个不是什么特别的技术,只是 一种设计理念。在面向对象的设计原则中,一般推荐模块之间基于接口编程,通常情况下调用方模块是不会感知到被调用方模块的内部具体实现。一旦代码里面涉及具体实现类,就违反了开闭原则。如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候不用在程序里面动态指明,这就需要一种服务发现机制。

吴声子夜歌的博客 3488

拆解一个800G光模块:手把手带你认识里面的MT插芯和光纤阵列(FA)

本文详细拆解了800G光模块中的关键组件MT插芯和光纤阵列(FA),揭示了并行光学技术如何实现高速数据传输。通过显微镜下的精密观察,展示了MT插芯的高精度构造和光纤阵列的连接奥秘,同时解析了高精度研磨工艺对光信号传输的重要性。这些微型组件是高速收发模块实现800Gbps带宽的核心技术。

weixin_30839881的博客 1539

Java SPI 机制详解

Java SPI机制是一种基于接口的服务发现机制,它允许应用程序在运行时发现并加载实现特定接口的服务提供者。SPI全称为,它是Java编程语言的一种规范。通过SPI机制,应用程序可以通过配置文件的方式,动态地加载实现了某个接口的类,并使用这些类提供的功能,而无需在代码中显式引用它们,这样就可以让应用程序具有更高的灵活性和可扩展性。首先,我们需要定义一个服务接口,这个接口定义了一组抽象方法,所有实现类都必须实现这些方法。在目录下,以服务接口全限定名为名称的文件,文件内容分别为实现类的全限定名。

一颗奋起萌发的种子的博客 1673

图形引擎实战:镭射效果制作分享

该方案的优势1.遵循物理的2.可以使用少量的参数去调控镭射的效果。计算相对简单,缺点仍是颜色不能够自定义,还有一种常见的方案是采样一张镭射贴图来模拟,好处是颜色可以自定义同时性能上还很高效没有复杂的公式计算。我查阅了网上普遍的方案,常见的做法是制作一个Ramp图生成工具,然后将生成好的ramp图赋给材质。这两个方案的好处是美术同学可以手动的去控制镭射的颜色。最后就是Shader中对Ramp图的采样的部分,Ramp图的UV坐标是使用fresnel作为Ramp的U轴坐标,0.5作为V轴坐标。

qq_41166022的博客 1350

Python 爬虫教程:抓取交通流量和道路信息

Scrapy是一个强大的爬虫框架,适用于大规模的爬虫任务。它提供了高效的数据抓取和存储功能,支持数据清洗和解析操作。Scrapy特别适合用于抓取开放的API接口和数据流。

2201_76125261的博客 2233

Java SPI机制详解

SPI全称Service Provider Interface,是Java提供的一种服务发现机制。实现服务接口和服务实现的解耦。 Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,实现不修改任何代码的情况下切换不同的实现。

不积跬步无以至千里,不积小流无以成江海。一个喜欢学习分享的程序员 3272

R语言coxph函数对加权数据集进行竞争风险模型拟合、regplot包的regplot函数可视化竞争风险回归模型的列线图nomogram、计算特定病例的事件概率(控制了竞争风险的累计事件概率)

R语言coxph函数对加权数据集进行竞争风险模型拟合、regplot包的regplot函数可视化竞争风险回归模型的列线图nomogram、计算特定病例的事件概率(控制了竞争风险的累计事件概率)

statistics+insight+vista+power 356

Java SPI机制

SPI的全称是Service Provider Interface,中文名为服务提供者接口,是一种服务发现机制,基于JDK内置的动态加载实现扩展点的机制。

csdn_fanhehua的博客 3173

SAP-ABAP:新手入门篇——从0到1写出你的第一个ABAP Hello World程序并完成调试运行

本文是ABAP开发入门指南,详细介绍如何创建并运行第一个"Hello World"程序。主要内容包括:通过SE38事务码进入ABAP编辑器;创建以Z/Y开头的程序并保存为本地对象;编写简单的WRITE输出语句;完成语法检查、激活和执行流程;以及初步体验调试功能。文章特别强调ABAP开发的规范要求,如程序命名规则、激活与保存的区别等,并提供了详细的步骤说明和常见问题提示,帮助零基础开发者快速上手ABAP编程。

baidu_35680696的博客 478

MobSF-v3源代码分析(一)

移动安全框架(MobSF)是一种自动化的移动应用程序(Android/iOS/Windows)测试框架,能够执行静态、动态和恶意软件分析。 它可用于Android、iOS和Windows移动应用程序的有效和快速安全分析,并支持二进制文件(APK,IPA和APPX)和压缩源代码。 MobSF可以在运行时为Android应用程序进行动态应用程序测试,并具有由CapFuzz(一种特定于Web API的安全扫描程序)提供支持的Web API模糊测试。MobSF旨在使您的CI/CD或DevSecOps管道集成无缝。

武天旭的博客 8959

聊聊 Java SPI 机制

当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。目录,该目录下会有一个名字为java.sql.Driver的文件,文件内容是com.mysql.cj.jdbc.Driver,这里面的内容就是针对Java中定义的接口的实现。,但是在其架构中并未对其做出具体的实现,而是交给不同的厂商去实现,比如JTA强一致性事务的。

码猿技术专栏 136

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

聊聊Java SPI机制

一、Java SPI机制 SPI(Service Provider Interface)是JDK内置的服务发现机制,用在不同模块间通过接口调用服务,避免对具体服务服务接口具体实现类的耦合。比如JDBC的数据库驱动模块,不同数据库连接驱动接口相同但实现类不同,在使用SPI机制以前调用驱动代码需要直接在类里采用Class.forName(具体实现类全名)的方式调用,这样调用方依赖了具体的驱动实现,在...

吒哇 6415

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

聊聊Java的SPI机制

SPI全称Service Provider Interface,翻译过来就是服务提供者接口。调用方提供接口声明,服务提供方对接口进行实现,提供服务的一种机制,服务提供方往往是第三方或者是外部扩展。下面是一段类的注释:翻译:服务提供者是对某一服务的具体实现。在服务提供者中的类一般会实现服务所定义的接口,并继承服务本身定义的类。服务提供者可以通过扩展的方式安装到Java平台的实现中,也就是说,将jar文件放置到标准的扩展目录之一。

分享Java,Rust相关技术 988

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

来聊聊Java的SPI机制吧

今天来聊聊Java中一个机制——SPI。 “你是不是因为键盘上的A和S相邻而打错字了?” 并不是,SPI和API有关系,但确实是两种不一样的东西。 What & Why SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,Java的SPI机制可以为某个接口寻找服务实现,这一机制的主要思想是将装配的控制权移到程序之外,核心目的是解耦。 这是对SPI的很抽象的定义,光看这一句应该看不懂,以下慢慢分析。 在OOP设计里,我们一般推荐模块之间基于接口编程,模块

多学多想 421

java spi机制_聊聊Java SPI机制

一、Java SPI机制SPI(Service Provider Interface)是JDK内置的服务发现机制,用在不同模块间通过接口调用服务,避免对具体服务服务接口具体实现类的耦合。比如JDBC的数据库驱动模块,不同数据库连接驱动接口相同但实现类不同,在使用SPI机制以前调用驱动代码需要直接在类里采用Class.forName(具体实现类全名)的方式调用,这样调用方依赖了具体的驱动实现,在替换...

weixin_39865952的博客 157

java spi机制_结合实战和源码来聊聊Java中的SPI机制?

点击上方蓝色“冰河技术”,关注并选择“设为星标”持之以恒,贵在坚持,每天进步一点点!作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了精准定时调度方案,经受住了生产环境的考验。为使更多童鞋受益,现给出开源框架地址:https://github.com/sunshinelyz/my...

weixin_39821874的博客 122

java spi机制_突破Java面试(36)-聊聊Dubbo的SPI机制

Dubbo中的SPI是什么?前面基础性的东西问完了,确定你应该了解Dubbo,那么自然问个稍微难的问题,就是SPI,先问问你这是个啥,然后问问你怎么实现的!其实就是看看你对dubbo的掌握如何SPI 全称为 Service Provider Interface,是一种服务发现机制.说白了是啥呢,比如你有个接口,该接口有3个实现类,那么在系统运行时,这个接口到底选择哪个实现类呢?这就需要SPI了,需...

weixin_40000999的博客 131

结合实战和源码来聊聊Java中的SPI机制?

一文搞懂Java的SPI机制!!

冰河的专栏 1063

java spi dubbo spi_突破Java面试(36)-聊聊Dubbo的SPI机制

1 面试题Dubbo中的SPI是什么?2 考点分析前面基础性的东西问完了,确定你应该了解Dubbo,那么自然问个稍微难的问题,就是SPI,先问问你这是个啥,然后问问你怎么实现的!其实就是看看你对dubbo的掌握如何3 SPI简介SPI 全称为 Service Provider Interface,是一种服务发现机制.说白了是啥呢,比如你有个接口,该接口有3个实现类,那么在系统运行时,这个接口到底选...

weixin_32564229的博客 264

「某大厂二面」聊聊Java中的SPI机制

SPI service provider interface的简写 SPI是什么 SPI主要解决的还是耦合问题。我们知道面向接口编程是很好的编程方式。 举一个简单的例子 假设有一天你一不小心成为了你们组的组长,这时候经理要求你开发一个对象存储服务,统一一下公司内部的云存储服务,首先市面上的云存储有好多家,例如阿里的OSS,t ...

你走过的路 风都是暖的 847
上一篇: 意难平啊7比0,老范都想跳江,如果我是教练我会怎么办
下一篇: 足协要为海参队包机澳洲参赛,我觉得游泳去比较适合他们
丰弟
博客等级 码龄11年 55粉丝 5原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值