从实现到原理,聊聊Java中的SPI动态扩展 上

简介: 从实现到原理,聊聊Java中的SPI动态扩展 上


八股文背多了,相信大家都听说过一个词,SPI 扩展

有的面试官就很喜欢问这个问题,SpringBoot 的自动装配是如何实现的?

基本上,你一说是基于 spring 的 SPI 扩展机制,再把spring.factories文件和EnableAutoConfiguration提一下,那么这个问题就答的八九不离十了。

就像四五年前,我去面试的时候被问到这个问题,SPI 动态扩展机制 这几个词从嘴里一说出来,就把面试官唬的一愣一愣的。可能他们也没见过这么能装逼的,一句话能简简单单说明白,非要拽一个听上去很高大上的词。

话说回来,被唬住的可不止是面试官,其实还有我自己。至于 SPI 扩展究竟是个啥,是怎么实现的,我当时也根本不明白。

不过现在的面试就是这样,对线八股文,要想唬住面试官,就得先唬住自己。

那么我们今天暂且不提 spring 的 SPI 扩展,先来看看 java 本身自带的 SPI 扩展机制是怎么一回事。

1、简介

SPI 的全称是Service Provider Interface,翻译过来就是服务提供者的接口 ,它所实现的其实是一种服务的发现机制。

这么说起来可能还是有点不好理解,我举个例子来类比一下。

在 spring 项目中,写 service 层代码前,会约定俗成的会添加一个接口层。然后通过 spring 中的依赖注入,可以借助@Autowired等方式注入这个接口的实现类的实例对象,之后对于 service 的调用一般也基于接口操作。

简单形容就是这样的:

如图所示,接口、实现类都是由服务提供方提供,我们可以把 controller 看作服务调用者,调用方只管调用接口就可以了。

虽然也有声音认为,大部分情况下 service 只有一个实现类,接口层显得有些多余。但是在《Head First Design Patterns》这本书中,大佬们还是建议过:

Program to an interface, not an implementation.

没错,就是常说的要面向接口编程 。至于好处,也不外乎是降低耦合度、方便日后扩展、提高了代码的灵活性和可维护性等等。

在上面这个例子里,这个接口层和其中的方法我们可以称之为API ,而我们要讨论的SPI 和它相比,有类似也有差异,还是先看图:

简单来说,就是服务的调用方定义一个接口规范,可以由不同的服务提供者实现。并且,调用方能够通过某种机制来发现服务提供方,并通过接口调用它的能力。

通过对比,我们可以看出它们虽然都有着接口 这一层面,但还是有很大的不同:

API 中的接口是服务提供者给服务调用者的一个功能列表,而 SPI 中更多强调的是,服务调用者对服务实现的一种约束,服务提供者根据这种约束实现的服务,可以被服务调用者发现。

说白了,Java 中的 SPI 实现的就是,你按我的接口规范实现服务,我就能通过某种机制为这个接口寻找到这个服务。

这么说起来可能还有些抽象,下面我们举一个例子,类比具体描述一下这个过程。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

2、定义接口

说起智能家居系统,大家现在都比较熟悉了,只要是相同品牌下的产品,连上 wifi 就能够通过手机 app 控制了,非常方便。

虽然产品不断更新换代,型号更新层出不穷,但是同种家电在 app 上操作起来,功能一般都是一样的。就拿空调来说,我们在 app 上操作起来一般也就三个主要功能:开关选模式调节温度

假设我现在在客厅、卧室、书房安装了 3 款不同型号的空调,并把它们都接入到了我 app 中,那么之后的操作都是相同的几个按键,简单粗暴。

思考一下,无论是开关还是调温,都是通过 app 去调用设备的接口罢了,那么如果不同型号的空调各写各的接口,后端 app 在开发的时候光对接接口都麻烦的要死。

解决方法也很简单,我先定义一套接口规范,不管你以后什么型号的空调,都按我的规范来实现接口。以后只要我能发现你的设备,那么都可以按相同的方法来调用接口。

那么下面就先来定义这么一套接口规范,如果你以后想要接入智能家居系统,那么就要遵循这个规范来开发接口。

新建一个项目作为标准,就叫aircondition-standard好了,然后创建一个接口。除了 3 个操作以外,我们再添加一个获取空调型号的方法。

public interface IAircondition {
    // 获取型号
    String getType();
    // 开关
    void turnOnOff();
    // 调节温度
    void adjustTemperature(int temperature);
    // 模式变更
    void changeModel(int modelId);
}

这个接口后面要给服务的实现方来使用,用 maven 把它打成 jar 包:

mvn clean install

之后服务提供者在项目中就可以引入这个 jar 包了,有了这套规范,就保证了产品后期不管怎么更新换代,都能接入到系统来。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

3、服务实现

制定并发布完规则后,挂式空调 作为第一个服务提供者就来了,新建一个项目aircondition-hanging-type,并引入刚才打好的 jar 包:

<dependency>
    <groupId>com.cn.hydra</groupId>
    <artifactId>aircondition-standard</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

创建服务类,并实现前面定义的接口:

public class HangingTypeAircondition
        implements IAircondition{
    public String getType() {
        return "HangingType";
    }
    public void turnOnOff() {
        System.out.println("挂式空调开关");
    }
    public void adjustTemperature(int i) {
        System.out.println("挂式空调调节温度");
    }
    public void changeModel(int i) {
        System.out.println("挂式空调更换模式");
    }
}

在项目的resources的目录下,创建META-INF/services目录,然后以前面定义的接口名com.cn.hydra.IAircondition创建文件,并在文件中写入实现类的全限定名。

com.cn.hydra.HangingTypeAircondition

整个项目结构非常简单:

这样,一个服务方的简单实现就搞定了,用 maven 打成 jar 包,之后就可以提供给调用方使用了。

同理,我们可以再创建一个立式空调 的项目aircondition-vertical-type,也只创建一个服务类:

public class VerticalTypeAircondition
        implements IAircondition{
    public String getType() {
        return "VerticalType";
    }
    public void turnOnOff() {
        System.out.println("立式空调开关");
    }
    public void adjustTemperature(int i) {
        System.out.println("立式空调调节温度");
    }
    public void changeModel(int i) {
        System.out.println("立式空调更换模式");
    }
}

还是按上面的命名规则,创建一个配置文件:

com.cn.hydra.VerticalTypeAircondition

同样,打成 jar 包就完事了,至于服务调用者如何去发现和调用这两个服务,下面详细再说。

相关文章
|
3月前
|
监控 Java API
现代 Java IO 高性能实践从原理到落地的高效实现路径与实战指南
本文深入解析现代Java高性能IO实践,涵盖异步非阻塞IO、操作系统优化、大文件处理、响应式网络编程与数据库访问,结合Netty、Reactor等技术落地高并发应用,助力构建高效可扩展的IO系统。
121 0
|
4月前
|
存储 缓存 Java
我们来详细讲一讲 Java NIO 底层原理
我是小假 期待与你的下一次相遇 ~
169 2
|
3月前
|
人工智能 安全 Java
Go与Java泛型原理简介
本文介绍了Go与Java泛型的实现原理。Go通过单态化为不同类型生成函数副本,提升运行效率;而Java则采用类型擦除,将泛型转为Object类型处理,保持兼容性但牺牲部分类型安全。两种机制各有优劣,适用于不同场景。
110 24
|
4月前
|
XML JSON Java
Java 反射:从原理到实战的全面解析与应用指南
本文深度解析Java反射机制,从原理到实战应用全覆盖。首先讲解反射的概念与核心原理,包括类加载过程和`Class`对象的作用;接着详细分析反射的核心API用法,如`Class`、`Constructor`、`Method`和`Field`的操作方法;最后通过动态代理和注解驱动配置解析等实战场景,帮助读者掌握反射技术的实际应用。内容翔实,适合希望深入理解Java反射机制的开发者。
410 13
|
3月前
|
存储 缓存 安全
深入讲解 Java 并发编程核心原理与应用案例
本教程全面讲解Java并发编程,涵盖并发基础、线程安全、同步机制、并发工具类、线程池及实际应用案例,助你掌握多线程开发核心技术,提升程序性能与响应能力。
162 0
|
4月前
|
算法 Java 索引
说一说 Java 并发队列原理剖析
我是小假 期待与你的下一次相遇 ~
|
5月前
|
人工智能 Java 关系型数据库
Java——SPI机制详解
SPI(Service Provider Interface)是JDK内置的服务提供发现机制,主要用于框架扩展和组件替换。通过在`META-INF/services/`目录下定义接口实现类文件,Java程序可利用`ServiceLoader`动态加载服务实现。SPI核心思想是解耦,允许不同厂商为同一接口提供多种实现,如`java.sql.Driver`的MySQL与PostgreSQL实现。然而,SPI存在缺陷:需遍历所有实现并实例化,可能造成资源浪费;获取实现类方式不够灵活;多线程使用时存在安全问题。尽管如此,SPI仍是Java生态系统中实现插件化和模块化设计的重要工具。
177 0
|
7月前
|
缓存 Dubbo Java
理解的Java中SPI机制
本文深入解析了JDK提供的Java SPI(Service Provider Interface)机制,这是一种基于接口编程、策略模式与配置文件组合实现的动态加载机制,核心在于解耦。文章通过具体示例介绍了SPI的使用方法,包括定义接口、创建配置文件及加载实现类的过程,并分析了其原理与优缺点。SPI适用于框架扩展或替换场景,如JDBC驱动加载、SLF4J日志实现等,但存在加载效率低和线程安全问题。
314 7
理解的Java中SPI机制
|
Dubbo Java 关系型数据库
Java SPI机制分析
文章深入分析了Java SPI机制,以JDBC为例,详细探讨了服务提供者接口的发现、加载过程,并提供了一个序列化服务的实战示例,展示了如何使用ServiceLoader进行服务发现和扩展。
100 3