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

深入理解 Java SPI - 概念、原理、应用

零、前言

在当今互联网时代,应用程序越来越复杂,对于我们开发人员来说,如何实现高效的组件化和模块化已经成为了一个重要的问题。而 Java SPI(Service Provider Interface)机制,作为一种基于接口的服务发现机制,可以帮助我们更好地解决这个问题。这样会程序具有高度的灵活性、解耦、可扩展性!

一、概念

SPI 机制,全称** Service Provider Interface**,即服务提供者接口,是从Java 6 开始引入的,一种基于 ClassLoader 来发现并加载服务的机制,是Java提供的一套用来被第三方实现或者扩展的API,可以用来启用框架扩展和替换组件,它的核心类是 java.util.ServiceLoader。
单从字面上看,可以这样理解,该机制提供了一种可根据接口类型去动态加载出接口实现类对象的功能。打一个比喻,该机制就类似Spring容器,通过IOC将对象的创建交给Spring容器处理,若需要获取某个类的对象,就从Spring容器里取出使用即可。同理,在SPI机制当中,提供了一个类似Spring容器的角色,叫**【服务提供者】**,在代码运行过程中,若要使用到实现了某个接口的服务实现类对象,只需要将对应的接口类型交给服务提供者,服务提供者将会动态加载出所有实现了该接口的服务实现类对象,最后给到服务使用者使用。
在大型系统设计中,开闭原则和解耦是必不可少的,而SPI机制的核心便是解耦合。通过SPI机制,将实现类隐藏在接口后面,根据需要寻找服务实现,SPI就提供了这样的服务发现机制。
Effective Java中也提到SPI是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。引入服务提供者就是引入了SPI接口的实现者,通过本地的注册发现获取到具体的实现类,轻松可插拔。
SPI 的核心思想是将服务接口与其具体实现分离,从而提升程序的扩展性和灵活性。这种机制通过配置文件和ServiceLoader来实现。具体来说,SPI机制主要由以下几个部分组成:

  1. Service:这是被第三方实现或扩展的API,是一个公开的接口或抽象类,定义了一个抽象的功能模块。
  2. Service Provider Interface:定义了服务接口。
  3. Service Provider:实现了Service Provider Interface的类。
  4. ServiceLoader:负责加载并实例化服务提供者的实现,是SPI机制的核心组件。

spi的运行流程.png

优缺点

优点:

  • 解耦性强:通过将服务接口与其实现分离,提高了系统的灵活性和可维护性。
  • 动态加载:可以在运行时动态加载和替换服务实现,增强了系统的可扩展性。
  • 标准化配置:通过配置文件的方式进行服务提供者的选择,简化了开发过程。

缺点:

  • 性能开销:由于需要加载和实例化多个实现类,可能会带来一定的性能开销。
  • 配置较麻烦:SPI需要在META-INF/services目录下创建配置文件,并将实现类的类名写入其中。这使得配置相对较为繁琐。
  • 安全性不足:SPI提供者必须将其实现类名称写入到配置文件中,因此如果未正确配置,则可能存在安全风险。

二、应用场景

Java SPI机制是一种服务提供者发现的机制,适用于需要在多个实现中选择一个进行使用的场景。
常见的应用场景包括:

应用名称具体应用场景
数据库驱动程序加载JDBC为了实现可插拔的数据库驱动,在Java.sql.Driver接口中定义了一组标准的API规范,而具体的数据库厂商则需要实现这个接口,以提供自己的数据库驱动程序。在Java中,JDBC驱动程序的加载就是通过SPI机制实现的。
日志框架的实现流行的开源日志框架,如Log4j、SLF4J和Logback等,都采用了SPI机制。用户可以根据自己的需求选择合适的日志实现,而不需要修改代码。
Spring框架Spring框架中的Bean加载机制就使用了SPI思想,通过读取classpath下的META-INF/spring.factories文件来加载各种自定义的Bean。
Dubbo框架Dubbo框架也使用了SPI思想,通过接口注解@SPI声明扩展点接口,并在classpath下的META-INF/dubbo目录中提供实现类的配置文件,来实现扩展点的动态加载。
MyBatis框架MyBatis框架中的插件机制也使用了SPI思想,通过在classpath下的META-INF/services目录中存放插件接口的实现类路径,来实现插件的加载和执行。
Netty框架Netty框架也使用了SPI机制,让用户可以根据自己的需求选择合适的网络协议实现方式。
Hadoop框架Hadoop框架中的输入输出格式也使用了SPI思想,通过在classpath下的META-INF/services目录中存放输入输出格式接口的实现类路径,来实现输入输出格式的灵活配置和切换。

Spring的SPI机制相对于Java原生的SPI机制进行了改造和扩展,主要体现在以下几个方面:

  • 支持多个实现类:Spring的SPI机制允许为同一个接口定义多个实现类,而Java原生的SPI机制只支持单个实现类。这使得在应用程序中使用Spring的SPI机制更加灵活和可扩展。
  • 支持自动装配:Spring的SPI机制支持自动装配,可以通过将实现类标记为Spring组件(例如@Component),从而实现自动装配和依赖注入。这在一定程度上简化了应用程序中服务提供者的配置和管理。
  • 支持动态替换:Spring的SPI机制支持动态替换服务提供者,可以通过修改配置文件或者其他方式来切换服务提供者。而Java原生的SPI机制只能在启动时加载一次服务提供者,并且无法在运行时动态替换。
  • 提供了更多扩展点:Spring的SPI机制提供了很多扩展点,例如BeanPostProcessor、BeanFactoryPostProcessor等,可以在服务提供者初始化和创建过程中进行自定义操作。

其他框架也是对Java SPI进行改造和扩展增强,从而更好的提供服务!

三、Java SPI 在JDBC 中的应用

SPI 机制广泛应用于各种框架和库中,例如JDBC、Dubbo等。它使得应用程序可以在不修改代码的情况下替换或扩展功能。例如,在数据库连接池中,可以通过SPI机制选择不同的数据库驱动。
JDBC,全称 Java DataBase Connectivity

  • JDBC 即使用java语言来访问数据库的一套API
  • 不同数据库厂商都会提供各自的JDBC实现

jdbc原理.png
熟悉的 Class.forName 加载驱动:
spi-jdbc.png
使用SPI后,只需要引入对应的依赖 JAR 包即可:
spi-jdbc-jar.png
SPI 的规范在 JDBC 中的实践
spi三大规范要素.png

四、实现方式

Java SPI 的实现依赖于ServiceLoader类。ServiceLoader通过使用线程上下文类加载器(Thread Context Class载荷器)来加载SPI接口的实现类。实现类的全路径名需配置在META-INF/services/目录下的相应文件中,例如META-INF/services/java.sql.Driver
具体步骤如下:

  1. META-INF/services/目录下创建一个以接口全限定名命名的文件。
  2. 在该文件中写入实现类的全限定名,每个实现类占一行。
  3. 使用ServiceLoader.load ()方法加载这些实现类,并通过反射机制实例化它们。

五、自定义 SPI 应用实例

spi自定义应用-需求.png
项目关系图
spi自定义应用-关系图.png

simple-isp-mobile
image.png

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.tyron</groupId><artifactId>java-spi-example</artifactId><version>{version}</version></parent><artifactId>simple-isp-mobile</artifactId><version>${revision}</version><name>simple-isp-mobile</name><description>中国移动服务商</description><dependencies><dependency><groupId>com.tyron</groupId><artifactId>simple-api</artifactId><version>{version}</version><scope>compile</scope></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target></configuration></plugin></plugins></build>
</project>

simple-isp-unicom
image.png
项目源码:https://gitee.com/tyronchen/spring-boot-learn/tree/master/simple_spi_example

为了充分发挥SPI机制的优势,建议遵循以下最佳实践:

  1. 合理配置服务提供者:确保每个服务接口只有一个明确的实现,并且该实现能够高效地完成任务。
  2. 优化加载策略:避免一次性加载所有可能的服务实现,可以采用按需加载的方式减少资源消耗。
  3. 统一配置格式:保持配置文件的一致性和规范性,便于管理和维护。

参考

Java SPI概念、实现原理、优缺点、应用场景、使用步骤、实战SPI案例
10分钟让你彻底明白Java SPI
Java SPI机制实战详解及源码分析

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • JavaScript基础(30)_事件的冒泡、事件的委派
  • 出行365:依托分布式数据库,让出行无忧 | OceanBase案例
  • ios语言基础
  • Android 通知栏推送功能
  • 学习笔记第二十二天
  • QT键盘和鼠标事件
  • 画菱形(曼哈顿距离)
  • 【深度学习】DeepSpeed,ZeRO 数据并行的三个阶段是什么?
  • mysql中B+树的数据存储
  • 【MySQL】最左前缀匹配原则
  • zdppy+vue3+onllyoffice开发文档管理系统项目实战 20240808 上课笔记
  • Java中的分布式日志与追踪
  • Postman Pre-request Script
  • 【Vue3】Pinia存储及读取数据
  • [Meachines] [Easy] valentine SSL心脏滴血+SSH-RSA解密+trp00f自动化权限提升+Tmux进程劫持权限提升
  • 9月CHINA-PUB-OPENDAY技术沙龙——IPHONE
  • “寒冬”下的金三银四跳槽季来了,帮你客观分析一下局面
  • 【跃迁之路】【444天】程序员高效学习方法论探索系列(实验阶段201-2018.04.25)...
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • iOS小技巧之UIImagePickerController实现头像选择
  • JavaScript服务器推送技术之 WebSocket
  • magento2项目上线注意事项
  • Python打包系统简单入门
  • 测试开发系类之接口自动化测试
  • 工程优化暨babel升级小记
  • 基于webpack 的 vue 多页架构
  • 体验javascript之美-第五课 匿名函数自执行和闭包是一回事儿吗?
  • 微信开放平台全网发布【失败】的几点排查方法
  • 我从编程教室毕业
  • 在weex里面使用chart图表
  • RDS-Mysql 物理备份恢复到本地数据库上
  • ​2021半年盘点,不想你错过的重磅新书
  • ​VRRP 虚拟路由冗余协议(华为)
  • ## 基础知识
  • #define MODIFY_REG(REG, CLEARMASK, SETMASK)
  • #前后端分离# 头条发布系统
  • $nextTick的使用场景介绍
  • (2)关于RabbitMq 的 Topic Exchange 主题交换机
  • (39)STM32——FLASH闪存
  • (day18) leetcode 204.计数质数
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (Matalb回归预测)PSO-BP粒子群算法优化BP神经网络的多维回归预测
  • (SpringBoot)第七章:SpringBoot日志文件
  • (八)c52学习之旅-中断实验
  • (附源码)spring boot网络空间安全实验教学示范中心网站 毕业设计 111454
  • (七)Flink Watermark
  • (十二)devops持续集成开发——jenkins的全局工具配置之sonar qube环境安装及配置
  • (转) SpringBoot:使用spring-boot-devtools进行热部署以及不生效的问题解决
  • (转)清华学霸演讲稿:永远不要说你已经尽力了
  • (转)视频码率,帧率和分辨率的联系与区别
  • ***php进行支付宝开发中return_url和notify_url的区别分析
  • .gitignore文件忽略的内容不生效问题解决
  • .NET 4.0网络开发入门之旅-- 我在“网” 中央(下)
  • .NET 4.0中使用内存映射文件实现进程通讯
  • .NET C# 操作Neo4j图数据库