网易面试:什么是SPI,SPI和API有什么区别?

news2024/12/19 7:09:56

说在前面

在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:

  • 什么是SPI,SPI和API有什么区别?

最近有小伙伴在面网易,又遇到了相关的面试题。小伙伴懵了, 他从来没有用过SPI,SO,面挂了。

所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。

当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典PDF》V119版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注公众号【技术自由圈】获取

文章目录

    • 说在前面
    • 何谓 SPI?
    • Java SPI 的应用Demo
    • SPI 使用场景
    • SPI 和 API 在使用上的区别?
    • SPI 和 API 在本质上的区别
    • SPI 源码分析
      • 1、SPI的核心就是`ServiceLoader.load()`方法
      • 2、ServiceLoader核心代码介绍
    • SPI 的优缺点?
    • 说在最后
    • 推荐阅读

何谓 SPI?

SPI 即 Service Provider Interface ,字面意思就是: “服务提供者的接口”,一般理解是:专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口。

SPI 的合作作用: 解耦。

SPI 将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方。

很多框架都使用了 Java 的 SPI 机制,比如: Spring 框架、数据库加载驱动、日志接口、以及Dubbo 的扩展实现等等。

Java SPI 的应用Demo

Java SPI机制

Java SPI机制

Java SPI 是JDK内置的一种服务提供发现机制。

我们一般希望模块直接基于接口编程,调用服务不直接硬编码具体的实现,而是通过为某个接口寻找服务实现的机制,通过它就可以实现,不修改原来jar的情况下, 为 API 新增一种实现。

Java SPI 有点类似 IOC 的思想,将装配的控制权移到了程序之外。

对于 Java 原生 SPI,只需要满足下面几个条件:

  • 1.定义服务的通用接口,针对通用的服务接口,提供具体的实现类

  • 2.在 src/main/resources/META-INF/services 或者 jar包的 META-INF/services/ 目录中,新建一个文件,文件名为 接口的全名。 文件内容为该接口的具体实现类的全名

  • 3.将 spi 所在 jar 放在主程序的 classpath 中

  • 4.服务调用方用java.util.ServiceLoader,用服务接口为参数,去动态加载具体的实现类到JVM中,然后就可以正常使用服务了

上面这一大段代码示例如下

1.接口和实现类

接口

public interface DemoService {
    void sayHello();
}

实现类

public class RedService implements DemoService{
    @Override
    public void sayHello() {
        System.out.println("red");
    }
}
public class BlueService implements DemoService{
    @Override
    public void sayHello() {
        System.out.println("blue");
    }
}

2.配置文件

META-INF/services文件夹下,路径名字一定分毫不差写对,配置文件名com.example.demo.spi.DemoService

文件内容

com.example.demo.spi.RedService
com.example.demo.spi.BlueService

3.jar包例如jdbc的需要导入classpath,我们这个示例程序自己写的代码就不用了

4.实际调用

public class ServiceMain {

    public static void main(String[] args) {
        ServiceLoader<DemoService> spiLoader = ServiceLoader.load(DemoService.class);
        Iterator<DemoService> iteratorSpi = spiLoader.iterator();
        while (iteratorSpi.hasNext()) {
            DemoService demoService = iteratorSpi.next();
            demoService.sayHello();
        }
    }
}

调用结果

red
blue

Java SPI 实际上是“基于接口的编程+ 配置文件”组合实现的动态加载机制。

SPI 有点类似 Spring IoC容器, 用于加载实例。

在 Spring IoC 容器中具有以下几种作用域:

  • singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例,适用于无状态bean;
  • prototype:原型模式,`每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例,适用于有状态的Bean;

但是SPI 与Spring 不同:

  • SPI 缺少实例的维护,作用域没有定义singleton和prototype的定义,不利于用户自由定制。

  • ServiceLoader不像 Spring,只能一次获取所有的接口实例, 不支持排序,随着新的实例加入,会出现排序不稳定的情况

SPI 使用场景

很多开源第三方jar包都有基于SPI的实现,在jar包META-INF/services中都有相关配置文件。

如下几个常见的场景:

1)JDBC加载不同类型的数据库驱动

2)Slf4j日志框架

3)Dubbo框架

看看 Dubbo 的扩展实现,就知道 SPI 机制用的多么广泛:

SPI 和 API 在使用上的区别?

那 SPI 和 API 有啥区别?

SPI 全称:Service Provider Interface , 服务提供接口

API 全称:Application Programming Interface, 即应用程序编程接口

说到 SPI 就不得不说一下 API 了,从广义上来说它们都属于接口,而且很容易混淆。

下面先用一张图说明一下:

一般模块之间都是通过接口进行通讯,

那我们在服务调用方和服务实现方(也称服务提供者)之间引入一个“接口”。

当实现方提供了接口和实现,我们可以通过调用实现方的接口从而拥有实现方给我们提供的能力,这就是 API ,这种接口和实现都是放在实现方的。

当接口存在于调用方这边时,就是 SPI ,由接口调用方确定接口规则,然后由不同的厂商去根绝这个规则对这个接口进行实现,从而提供服务。

SPI 和 API 在本质上的区别

SPI 区别于API模式,本质是一种服务接口规范定义权的转移,从服务提供者转移到服务消费者。

怎么理解呢?

API指: Provider 定义接口

服务提供方定义接口规范并按照接口规范完成服务具体实现,消费者需要遵守提供者的规则约束,否则无法消费

SPI指:consumer 定义接口

由消费方定义接口规范,服务提供者需要按照消费者定义的规范完成具体实现。否则无法消费。

SPI从理论上看,是一种接口定义和实现解耦的设计思路,以便于框架的简化和抽象;从实际看,是让服务提供者把接口规范定义权交岀去,至于交给谁是不一定的。

SPI定义权可以是服务消费者,也可以是任何一个第三方。一旦接口规范定义以后,只有消费者和服务提供者都遵循接口定义,才能匹配消费。

两者唯一的差别,在于服务提供者和服务消费者谁更加强势,仅此而已。

举个不恰当的例子:A国是C国工业制成品的消费国,C国只能提供相比A国更具性价比的产品,担心生产的产品会无法在A国销售。这时候,生产者必须遵守A国的生产标准。

谁有主动权,谁就有标准的制定权。在系统架构层面:谁是沉淀通用能力的平台方,谁就是主动权一方。

SPI 源码分析

1、SPI的核心就是ServiceLoader.load()方法

总结如下:

  1. 调用ServiceLoader.load(),创建一个ServiceLoader实例对象
  2. 创建LazyIterator实例对象lookupIterator
  3. 通过lookupIterator.hasNextService()方法读取固定目录META-INF/services/下面service全限定名文件,放在Enumeration对象configs
  4. 解析configs得到迭代器对象Iterator<String> pending
  5. 通过lookupIterator.nextService()方法初始化读取到的实现类,通过Class.forName()初始化

从上面的步骤可以总结以下几点

  1. 实现类工程必须创建定目录META-INF/services/,并创建service全限定名文件,文件内容是实现类全限定名
  2. 实现类必须有一个无参构造函数

2、ServiceLoader核心代码介绍

public final class ServiceLoader<S>
    implements Iterable<S>
{

    private static final String PREFIX = "META-INF/services/";

    // The class or interface representing the service being loaded
    private final Class<S> service;

    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;

    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;

    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;

public static <S> ServiceLoader<S> load(Class<S> service,
                                        ClassLoader loader)
{
    return new ServiceLoader<>(service, loader);
}

public void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
}

private ServiceLoader(Class<S> svc, ClassLoader cl) {
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
}

通过方法iterator()生成迭代器,内部调用LazyIterator实例对象

public Iterator<S> iterator() {
    return new Iterator<S>() {

        Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();

        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }

        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    };
}

内部类LazyIterator,读取配置文件META-INF/services/

private class LazyIterator
        implements Iterator<S>
    {

    Class<S> service;
    ClassLoader loader;
    Enumeration<URL> configs = null;
    Iterator<String> pending = null;
    String nextName = null;

    private LazyIterator(Class<S> service, ClassLoader loader) {
        this.service = service;
        this.loader = loader;
    }

    private boolean hasNextService() {
        if (nextName != null) {
            return true;
        }
        if (configs == null) {
            try {
                String fullName = PREFIX + service.getName();
                if (loader == null)
                    configs = ClassLoader.getSystemResources(fullName);
                else
                    configs = loader.getResources(fullName);
            } catch (IOException x) {
                fail(service, "Error locating configuration files", x);
            }
        }
        while ((pending == null) || !pending.hasNext()) {
            if (!configs.hasMoreElements()) {
                return false;
            }
            pending = parse(service, configs.nextElement());
        }
        nextName = pending.next();
        return true;
    }

    private S nextService() {
        if (!hasNextService())
            throw new NoSuchElementException();
        String cn = nextName;
        nextName = null;
        Class<?> c = null;
        try {
            c = Class.forName(cn, false, loader);
        } catch (ClassNotFoundException x) {
            fail(service,
                 "Provider " + cn + " not found");
        }
        if (!service.isAssignableFrom(c)) {
            fail(service,
                 "Provider " + cn  + " not a subtype");
        }
        try {
            S p = service.cast(c.newInstance());
            providers.put(cn, p);
            return p;
        } catch (Throwable x) {
            fail(service,
                 "Provider " + cn + " could not be instantiated",
                 x);
        }
        throw new Error();          // This cannot happen
    }

    public boolean hasNext() {
        if (acc == null) {
            return hasNextService();
        } else {
            PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                public Boolean run() { return hasNextService(); }
            };
            return AccessController.doPrivileged(action, acc);
        }
    }

    public S next() {
        if (acc == null) {
            return nextService();
        } else {
            PrivilegedAction<S> action = new PrivilegedAction<S>() {
                public S run() { return nextService(); }
            };
            return AccessController.doPrivileged(action, acc);
        }
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }
}

SPI 的优缺点?

通过 SPI 机制能够大大地提高接口设计的灵活性,

但是 SPI 机制也存在一些缺点,比如:

  • 需要遍历加载所有的实现类,不能做到按需加载,这样效率还是相对较低的。
  • 当多个 ServiceLoader 同时 load 时,会有并发问题。
  • SPI 缺少实例的维护,作用域没有定义singleton和prototype的定义,不利于用户自由定制。
  • ServiceLoader不像 Spring,只能一次获取所有的接口实例, 不支持排序,随着新的实例加入,会出现排序不稳定的情况,作用域没有定义singleton和prototype的定义,不利于用户自由定制

说在最后

SPI 面试题,是非常常见的面试题。

以上的内容,如果大家能对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。

在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》,并且在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。

最终,让面试官爱到 “不能自已、口水直流”。offer, 也就来了。

推荐阅读

《百亿级访问量,如何做缓存架构设计》

《多级缓存 架构设计》

《消息推送 架构设计》

《阿里2面:你们部署多少节点?1000W并发,当如何部署?》

《美团2面:5个9高可用99.999%,如何实现?》

《网易一面:单节点2000Wtps,Kafka怎么做的?》

《字节一面:事务补偿和事务重试,关系是什么?》

《网易一面:25Wqps高吞吐写Mysql,100W数据4秒写完,如何实现?》

《亿级短视频,如何架构?》

《炸裂,靠“吹牛”过京东一面,月薪40K》

《太猛了,靠“吹牛”过顺丰一面,月薪30K》

《炸裂了…京东一面索命40问,过了就50W+》

《问麻了…阿里一面索命27问,过了就60W+》

《百度狂问3小时,大厂offer到手,小伙真狠!》

《饿了么太狠:面个高级Java,抖这多硬活、狠活》

《字节狂问一小时,小伙offer到手,太狠了!》

《收个滴滴Offer:从小伙三面经历,看看需要学点啥?》

《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》PDF,请到下面公号【技术自由圈】取↓↓↓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1121808.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

推理引擎之模型压缩浅析

目录 前言1. 模型压缩架构和流程介绍2. 低比特量化原理2.1 量化基础介绍2.2 量化方法2.3 量化算法原理2.4 讨论 3. 感知量化训练QAT原理3.1 QAT原理3.2 量化算子插入3.3 QAT训练流程3.4 QAT衍生研究3.5 讨论 4. 训练后量化PTQ4.1 动态PTQ4.2 静态PTQ4.3 KL散度实现静态PTQ4.4 量…

PyTorch入门教学——Transforms使用

1、Transforms简介 Transforms在是计算机视觉工具包torchvision下的包&#xff0c;常用于对图像进行预处理&#xff0c;提高泛化能力。具体有&#xff1a; 数据中心化、数据标准化、缩放、裁剪、旋转、翻转、填充、噪声添加、灰度变换、线性变换、仿射变换和亮度、饱和度及对比…

springboot 程序设计优雅退出

一 springboot优雅退出 1.1 概述 在springboot2.3版本后&#xff0c;实现了优雅退出功能。当server.shutdowngraceful启用时&#xff0c;在 web 容器关闭时&#xff0c;web 服务器将不再接收新请求&#xff0c;并将剩余活动执行完成给设置一个缓冲期。缓冲期 timeout-per-shu…

Mysql数据库 2.SQL语言 数据类型与字段约束

Mysql数据类型 数据类型&#xff1a;指的是数据表中的列文件支持存放的数据类型 1.数值类型 Mysql当中有多种数据类型可以存放数值&#xff0c;不同的类型存放的数值的范围或者形式是不同的 注&#xff1a;前三种数字类型我们在实际研发中用的很少&#xff0c;一般整数类型…

2525.根据规则将箱子分类/并查集/动态规划

2525. 根据规则将箱子分类 - 力扣&#xff08;LeetCode&#xff09; 给你四个整数 length &#xff0c;width &#xff0c;height 和 mass &#xff0c;分别表示一个箱子的三个维度和质量&#xff0c;请你返回一个表示箱子 类别 的字符串。 如果满足以下条件&#xff0c;那么…

AndroidStudio如何设置中文

AndroidStudio如何设置中文 文章目录 AndroidStudio如何设置中文一、前言二、如何安装中文插件参考资料&#x1f498; 一、前言 Android Studio 是一个为 Android 平台开发程序的集成开发环境&#xff08;IDE&#xff09;。2013年5月16日在 Google I/O 上发布&#xff0c;可供…

springboot+vue开发的视频弹幕网站动漫网站

springbootvue开发的视频弹幕网站动漫网站 演示视频 https://www.bilibili.com/video/BV1MC4y137Qk/?share_sourcecopy_web&vd_source11344bb73ef9b33550b8202d07ae139b 功能&#xff1a; 前台&#xff1a; 首页&#xff08;猜你喜欢视频推荐&#xff09;、轮播图、分类…

四、网络请求与路由

一、网络请求 1、Axios请求 Axios是一个基于promise的网络请求库 &#xff08;1&#xff09;安装 npm install --save axios&#xff08;2&#xff09;引入 import axios from "axios"全局引入 import axios from "axios" import { createApp } from …

深度学习_3_实战_房价预测

梯度 实战 代码&#xff1a; # %matplotlib inline import random import torch import matplotlib.pyplot as plt # from d21 import torch as d21def synthetic_data(w, b, num_examples):"""生成 Y XW b 噪声。"""X torch.normal(0,…

面试知识储备--打包工具篇(webpack和vite)

1.vite常用配置 常用配置 1.preprocessorOptions 传递给 CSS 预处理器的配置选项 2.PostCSS 也是用来处理 CSS 的&#xff0c;只不过它更像是一个工具箱&#xff0c;可以添加各种插件来处理 CSS 3.resolve.extensions 导入时想要省略的扩展名列表。默认值为 [‘.mjs’, ‘.js’…

小团队之间有哪些好用免费的多人协同办公软件

在小团队协作中&#xff0c;选择适合的多人协同办公软件是提高工作效率和团队协作的重要一环。幸运的是&#xff0c;市场上有许多大多数功能都免费的多人协同办公软件&#xff0c;为小团队提供了强大的协作功能和便捷的工作环境。 在本文中&#xff0c;我将根据自己多年的在线…

2.2.C++项目:网络版五子棋对战之数据管理模块的设计

文章目录 一、数据管理模块实现&#xff08;一&#xff09;功能 二、设计&#xff08;一&#xff09;数据库设计&#xff08;二&#xff09;创建user_table类 一、数据管理模块实现 &#xff08;一&#xff09;功能 数据管理模块主要负责对于数据库中数据进行统一的增删改查管…

3ds Max2023安装教程(最新最详细)

目录 一.简介 二.安装步骤 软件&#xff1a;3ds Max版本&#xff1a;2023语言&#xff1a;简体中文大小&#xff1a;6.85G安装环境&#xff1a;Win11/Win10/Win8/Win7硬件要求&#xff1a;CPU3GHz 内存16G(或更高&#xff09;下载通道①百度网盘丨64位下载链接&#xff1a; …

【蓝桥每日一题]-动态规划 (保姆级教程 篇10)#方格取数

高能预警&#xff1a;讲了这么久动态规划了&#xff0c;该上点有难度的题吧 目录 题目&#xff1a;方格取数 思路&#xff08;解法一&#xff09;&#xff1a; 解法二&#xff1a; 题目&#xff1a;方格取数 思路&#xff08;解法一&#xff09;&#xff1a; 如果只有两个方向…

openCV Cuda

下载 git clone https://github.com/opencv/opencv.git git clone https://github.com/opencv/opencv_contrib.git确保准备好以下内容 1&#xff1a; visual studio &#xff08;不是vs code&#xff09; 2&#xff1a;下载后的两个包裹会放在以下结构 这样放的原因是我Ub…

Java EE-使用Servlet搭建一个简单的前后端交互程序

上述前端和后端的代码如下&#xff1a; 前端&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"vie…

数据库笔记——SQL语言DQL语句

schema等于database 数据库 datagrip中使用控制台进行操作&#xff1a; 右键new QueryConsole 创建表格create table中&#xff1a; 1. 括号内不管是定义属性还是声明约束&#xff0c;都使用逗号分隔&#xff0c;最后一句不用逗号 2. 括号外使用分号 DDL&#xff1a;数据库定…

python接口自动化测试(单元测试方法)

一、环境搭建 python unittest requests实现http请求的接口自动化Python的优势&#xff1a;语法简洁优美, 功能强大, 标准库跟第三方库灰常强大&#xff0c;建议大家事先了解一下Python的基础;unittest是python的标准测试库&#xff0c;相比于其他测试框架是python目前使用最广…

思辨:移动开发的未来在哪?

前段时间在知乎看到关于移动开发未来的问题&#xff0c;就尝试回答了一下&#xff0c;也触发了我对移动开发未来的思考。 移动开发未来怎么样? - 知乎 https://www.zhihu.com/question/613842211 什么是移动开发&#xff1f; 我们口中说的移动开发是什么&#xff0c;从广义和…

项目管理实战总结(二)-沟通路径

在一个大型的项目管理中&#xff0c;不同的沟通路径&#xff0c;会对整个事情的进展形成不同的影响。从项目管理的视角来看&#xff0c;该如何驱动项目有效进展&#xff0c;失之毫厘谬以千里。 沟通路径&#xff1a;调查问卷的推动事宜 在项目进行到了后期&#xff0c;甲方希…