你应该了解的 Java SPI 机制

前言

不知大家现在有没有去公司复工,我已经在家办公将近 3 周了,同时也在家呆了一个多月;还好工作并没有受到任何影响,我个人一直觉得远程工作和 IT 行业是非常契合的,这段时间的工作效率甚至比在办公室还高,同时由于我们公司的业务在海外,所以疫情几乎没有造成太多影响。

扯远了,这次主要是想和大家分享一下 JavaSPI 机制。周末没啥事,我翻了翻我之前的写的博客 《设计一个可拔插的 IOC 容器》,发现当时的实现并不那么优雅。

还没看过的朋友的我先做个前景提要,当时的需求:

我实现了一个类似于的 SpringMVC 但却很轻量的 http 框架 cicada,其中当然也需要一个 IOC 容器,可以存放所有的单例 bean。

这个 IOC 容器的实现我希望可以有多种方式,甚至可以提供一个接口供其他人实现;当然切换这个 IOC 容器的过程肯定是不能存在硬编码的,也就是这里所提到的可拔插
当我想使用 A 的实现方式时,我就引入 A 的 jar 包,使用 B 时就引入 B 的包。

先给大家看看两次实现的区别,先从代码简洁程度来说就是 SPI 更胜一筹。

什么是 SPI

在具体分析之前还是先了解下 SPI 是什么?

首先它其实是 Service provider interface 的简写,翻译成中文就是服务提供发现接口。

不过这里不要被这个名词搞混了,这里的服务发现和我们常听到的微服务中的服务发现并不能划等号。

就如同上文提到的对 IOC 容器的多种实现方式 A、B、C(可以把它们理解为服务),我需要在运行时知道应该使用哪一种具体的实现。

其实本质上来说这就是一种典型的面向接口编程,这一点在我们刚开始学习编程的时候就被反复强调了。

SPI 实践

接下来我们来如何来利用 SPI 实现刚才提到的可拔插 IOC 容器。

既然刚才都提到了 SPI 的本质就是面向接口编程,所以自然我们首先需要定义一个接口:

其中包含了一些 Bean 容器所必须的操作:注册、获取、释放 bean。

为了让其他人也能实现自己的 IOC 容器,所以我们将这个接口单独放到一个 Module 中,可供他人引入实现。

所以当我要实现一个单例的 IOC 容器时,我只需要新建一个 Module 然后引入刚才的模块并实现 CicadaBeanFactory 接口即可。

当然其中最重要的则是需要在 resources 目录下新建一个 META-INF/services/top.crossoverjie.cicada.base.bean.CicadaBeanFactory 文件,文件名必须得是我们之前定义接口的全限定名(SPI 规范)。

其中的内容便是我们自己实现类的全限定名:

top.crossoverjie.cicada.bean.ioc.CicadaIoc

可以想象最终会通过这里的全限定名来反射创建对象。

只不过这个过程 Java 已经提供 API 屏蔽掉了:

    public static CicadaBeanFactory getCicadaBeanFactory() {
        ServiceLoader<CicadaBeanFactory> cicadaBeanFactories = ServiceLoader.load(CicadaBeanFactory.class);
        if (cicadaBeanFactories.iterator().hasNext()){
            return cicadaBeanFactories.iterator().next() ;
        }

        return new CicadaDefaultBean();
    }

classpath 中存在我们刚才的实现类(引入实现类的 jar 包),便可以通过 java.util.ServiceLoader 工具类来找到所有的实现类(可以有多个实现类同时存在,只不过通常我们只需要一个)。



一些都准备好之后,使用自然就非常简单了。

    <dependency>
        <groupId>top.crossoverjie.opensource</groupId>
        <artifactId>cicada-ioc</artifactId>
        <version>2.0.4</version>
    </dependency>

我们只需要引入这个依赖便能使用它的实现,当我们想换一种实现方式时只需要更换一个依赖即可。

这样就做到了不修改一行代码灵活的可拔插选择 IOC 容器了。

SPI 的一些其他应用

虽然平时并不会直接使用到 SPI 来实现业务,但其实我们使用过的绝大多数框架都会提供 SPI 接口方便使用者扩展自己的功能。

比如 Dubbo 中提供一系列的扩展:

同类型的 RPC 框架 motan 中也提供了响应的扩展:

他们的使用方式都和 Java SPI 非常类似,只不过原理略有不同,同时也新增了一些功能。

比如 motanspi 允许是否为单例等等。

再比如 MySQL 的驱动包也是利用 SPI 来实现自己的连接逻辑。

总结

Java 自身的 SPI 其实也有点小毛病,比如:

  • 遍历加载所有实现类效率较低。
  • 当多个 ServiceLoader 同时 load 时会有并发问题(虽然没人这么干)。

最后总结一下,SPI 并不是某项高深的技术,本质就是面向接口编程,而面向接口本身在我们日常开发中也是必备技能,所以了解使用 SPI 也是很用处的。

本文所有源码:

https://github.com/TogetherOS/cicada

你的点赞与分享是对我最大的支持

原文地址:https://www.cnblogs.com/crossoverJie/p/12355485.html

时间: 2024-11-05 23:36:37

你应该了解的 Java SPI 机制的相关文章

JAVA SPI机制分析

简介 SPI的全名为Service Provider Interface,主要是应用于厂商自定义组件或插件中.在java.util.ServiceLoader的文档里有比较详细的介绍.简单的总结下java SPI机制的思想:我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块.xml解析模块.jdbc模块等方案.面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码.一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码.为

Java SPI机制和使用示例

JAVA SPI 简介 SPI 是 Java 提供的一种服务加载方式,全名为 Service Provider Interface.根据 Java 的 SPI 规范,我们可以定义一个服务接口,具体的实现由对应的实现者去提供,即服务提供者.然后在使用的时候再根据 SPI 的规范去获取对应的服务提供者的服务实现.通过 SPI 服务加载机制进行服务的注册和发现,可以有效的避免在代码中将服务提供者写死.从而可以基于接口编程,实现模块间的解耦. SPI 机制的约定 1 在 META-INF/service

Java SPI机制简介

SPI是Service Provider Interfaces的简称.根据Java的SPI规范,我们可以定义一个服务接口,具体的实现由对应的实现者去提供,即Service Provider(服务提供者).然后在使用的时候只要根据SPI的规范去获取对应的服务提供者的服务实现即可.为了便于理解,我们先来看一个使用SPI的示例.下载 假设我们有一个日志服务LogService,其只定义了一个info方法用于输出日志信息,我们希望把它作为SPI,然后具体的实现由对应的服务提供者去实现.LogServic

Java SPI机制原理和使用场景

SPI的全名为Service Provider Interface.这个是针对厂商或者插件的.一般来说对于未知的实现或者对扩展开放的系统,通常会把一些东西抽象出来,抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块.jdbc模块的方案等.这个可以通过我们的抽象工厂方法来理解这个含义,实现是可以又厂商或者开发人员自己实现.由于代码上是处于上层的一个封装者,是不会知道底层怎么去实现,那么只能通过spi的形式,让上层知道应该调用哪个抽象的具体实现.所以这里可以理解为某些jar

Java的SPI机制与简单的示例

一.SPI机制 这里先说下SPI的一个概念,SPI英文为Service Provider Interface单从字面可以理解为Service提供者接口,正如从SPI的名字去理解SPI就是Service提供者接口:我对SPI的定义:提供给服务提供厂商与扩展框架功能的开发者使用的接口. 在我们日常开发的时候都是对问题进行抽象成Api然后就提供各种Api的实现,这些Api的实现都是封装与我们的Jar中或框架中的虽然当我们想要提供一种Api新实现时可以不修改原来代码只需实现该Api就可以提供Api的新实

高级开发必须理解的Java中SPI机制

https://www.jianshu.com/p/46b42f7f593c 本文通过探析JDK提供的,在开源项目中比较常用的Java SPI机制,希望给大家在实际开发实践.学习开源项目提供参考. 1 SPI是什么 SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件. 整体机制图如下: Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制. 系统设计的各个抽象,往

Dubbo的SPI机制与JDK机制的不同及原理分析

从今天开始,将会逐步介绍关于DUbbo的有关知识.首先先简单介绍一下DUbbo的整体概述. 概述 Dubbo是SOA(面向服务架构)服务治理方案的核心框架.用于分布式调用,其重点在于分布式的治理. 简单的来说,可以把它分为四个角色.服务提供方(Provider).服务消费方(Consumer).注册中心和监控中心.通过注册中心对服务进行注册和订阅,通过监控中心对服务进行监控. 核心功能 Remoting:远程通讯,提供对多种NIO框架抽象封装,包括"同步转异步"和"请求-响应

java 的SPI机制

今天看到spring mvc 使用Java Validation Api(JSR-303)进行校验,需要加载一个 其具体实现(比如Hibernate Validator), 本来没有什么问题,但是突然想到这其中到底是怎样一种加载过程呢,也就是说spring为什么能够找到Hibernate Validator来作为JSR-303的具体实现的呢? 1. java中的SPI机制 这篇文章对java的SPI机制讲的比较容易理解,就不多做记录. http://www.cnblogs.com/javaee6

java中的SPI机制

1.定义 SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制. 一个服务(Service)通常指的是已知的接口或者抽象类,服务提供方就是对这个接口或者抽象类的实现,然后按照SPI 标准存放到资源路径META-INF/services目录下,文件的命名为该服务接口的全限定名. 2.SPI机制的约定 在META-INF/services/目录中创建以Service接口全限定名命名的文件,该文件内容为Service接口具体实现类的全限定名,文