spi你认识吗

初念初恋      2022-02-13     775

关键词:

人生有涯,学海无涯

一、SPI是什么

SPI是相对API而言的。

API指的是应用对服务调用方提供的接口,用于提供某种服务、功能,面向的是服务调用方。

SPI指的是应用对服务实现方提供的接口,用于实现某种服务、功能,面向的是服务实现方

二、SPI的使用

2.1、创建服务接口

package com.jw.spi;

public interface Fruit {
    String getName();
}

2.2、创建多个服务实现

package com.jw.spi;

public class Apple implements Fruit {
    @Override
    public String getName() {
        return "apple";
    }
}
package com.jw.spi;

public class Banana implements Fruit {
    @Override
    public String getName() {
        return "Banana";
    }
}

这里的两个服务实现类,针对的是两个服务实现方,一方实现了Apple,另一方实现了Banana。

2.3、创建配置文件

在resource下创建/META-INF/services目录,在services目录下创建以服务接口全限定名为名称的文件:com.jw.spi.Fruit

文件内容为,当前服务实现的服务实现者类的全限定名

com.jw.spi.Apple

2.4、创建测试类

public class Test {

    public static void main(String[] args) {
        ServiceLoader<Fruit> s = ServiceLoader.load(Fruit.class);
        Iterator<Fruit> it = s.iterator();
        while(it.hasNext()){
            System.out.println(it.next().getName());
        }
    }
}

执行结果为:

 apple

三、SPI的实现原理

SPI的实现主要依靠的就是ServiceLoader类。使用该类加载接口类型(例如:Fruit.class)

ServiceLoader<Fruit> s = ServiceLoader.load(Fruit.class);

虽然是一个load方法,但是并没有加载到指定的服务实现类,这里仅仅是对加载服务实现类做一些准备工作:

下面分析一下ServiceLoader类的源码:

public final class ServiceLoader<S>
    implements Iterable<S>
{
    // 扫描目录前缀
    private static final String PREFIX = "META-INF/services/";

    // 被加载的类或接口
    private final Class<S> service;

    // 用于定位、加载和实例化实现方实现的类的类加载器
    private final ClassLoader loader;

    // 上下文对象
    private final AccessControlContext acc;

    // 按照实例化的顺序缓存已经实例化的类
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // 懒查找迭代器
    private LazyIterator lookupIterator;
    
    
    // 创建ServiceLoader    
    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

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

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

    public void reload() {
        // 清空providers缓存
        providers.clear();
        // 为lookupIterator赋值,其实就是创建一个LazyIterator延迟迭代器。
        lookupIterator = new LazyIterator(service, loader);
    }

    // 私有内部类,提供对所有的service的类的加载与实例化
    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;
        }
        
      ....
        
}

然后创建迭代器:Iterator<Fruit> it = s.iterator();

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();
        }

    };
}

iterator方法中采用了匿名内部类的方式定义了一个新的迭代器,这个迭代器中每一个方法都是通过调用之前创建好的延迟迭代器lookupIterator来完成的

最后就是进行迭代加载了。

while(it.hasNext()){
    System.out.println(it.next().getName());
}
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);
    }
}

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;
}

hasNext方法调用了延迟迭代器的hasNext方法,内部调用了hasNextService方法,在这个方法中就会设法去找到指定名称(META-INF/services/+接口全限定名)的资源文件。并完成读取文件内容的操作。

然后执行it.next()操作,这个又会调用延迟迭代器的对应方法hasNext,内部调用了nextService方法,这个方法主要功能就是加载上面从文件中读取到的全限定名称表示的类。并生成实例,将实例保存到providers中。

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);
    }
}

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中
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
             x);
    }
    throw new Error();          // This cannot happen
}

四、总结

关于spi的详解到此就结束了,总结下spi能带来的好处:

  • 不需要改动源码就可以实现扩展,解耦。
  • 实现扩展对原来的代码几乎没有侵入性。
  • 只需要添加配置就可以实现扩展,符合开闭原则。

spi认识

SPI分为好几种模式 不同模式下 读取数据时 SCK线的状态不一样 STM32硬件SPI 有16BIT 8BIT的两种模式 既 收发都是8或16bit SPI缺少响应反馈机制 无法知道 是否收到数据 STM32的SPI还与I2S共用... 查看详情

有你认识的那个ta吗?

前言写公众号2年多了,认识了很多全国各地的读者朋友,也认识了不少热爱分享、一直在写作的公众号作者。下面的这4位,有你们认识的吗?8号线攻城狮公众号『8号线攻城狮』号主王工,嵌入式硬件工程师,目前主要从事嵌... 查看详情

2018年想要更高的工作效率吗?可以认识这些管理软件

2018年想要更高的工作效率吗?可以认识这些管理软件为了聊天,你认识了微信;为了打车,你认识了滴滴;为了吃饭,你认识了美团;为了追剧,你认识了优酷;为了工作更加高效,我觉得你应该认识一下点晴技术公司的管理... 查看详情

你真的认识innodbonlineddl吗?(代码片段)

最近在给一个大表增加字段的时候用到了OnlineDDL,在操作时有些疑问,带着这些问题查阅了官方文档并做一些总结,方便后面可以对DDL操作做好安全风险评估.一、定义先说说几个名词的定义DDL:数据定义或数据描述语言.比如使用如下... 查看详情

你真的认识innodbonlineddl吗?(代码片段)

最近在给一个大表增加字段的时候用到了OnlineDDL,在操作时有些疑问,带着这些问题查阅了官方文档并做一些总结,方便后面可以对DDL操作做好安全风险评估.一、定义先说说几个名词的定义DDL:数据定义或数据描述语言.比如使用如下... 查看详情

这几种游戏类型,你认识吗?

RPG,中文翻译为角色扮演,指的是玩家通过扮演游戏中的某一角色来完成游戏的完整内容,这类游戏的特点是有较长的剧情主线,以及需要较多的时间为角色练级。战斗方式基本都为回合制,战斗方式为指令战斗,并且等级在游... 查看详情

你真的懂threadpoolexecutor线程池技术吗?看了源码你会有全新的认识(代码片段)

Java是一门多线程的语言,基本上生产环境的Java项目都离不开多线程。而线程则是其中最重要的系统资源之一,如果这个资源利用得不好,很容易导致程序低效率,甚至是出问题。有以下场景,有个电话拨打系统,有一堆需要拨... 查看详情

spi和普通串口可以通信吗?

...寄存器(可编程)它的数据需要通过三线式串口(类似于SPI,一个时钟输入、一个载入脉冲、一个数据输入口)写入参数。我选用的单片机有两个普通串口,那么我可以把AD的SPI口接在单片机的普通串口上吗?还是通过单片机的... 查看详情

用fpga模拟spi从机通信中有时钟分频吗

...够欣赏验证的益处。还有什么可问的问题参考技术Axiinx有SPIIP核直接调用一下 查看详情

你真的了解ip地址吗?(代码片段)

...专栏⭐️《计算机网络》⭐️学习指南:IP协议基本认识分类地址多播地址无分类地址划分方式子网掩码IP分片与重组IPv6基本认识IPv4首部与IPv6首部IP协议相关技术DNS域名解析的工作流程ARP如何从IP地址找出其对应的MAC地址ÿ... 查看详情

你认识一个 16 位的时间戳吗?

】你认识一个16位的时间戳吗?【英文标题】:doyourecognizea16-digittimestamp?【发布时间】:2011-03-1405:55:24【问题描述】:我正在使用Googlebookmakrs,它返回16位时间戳,我似乎无法在C#中识别它以转换为真实日期,有什么想法吗?如何... 查看详情

你真的了解爬虫吗?看完你会对网络爬虫有更深更全面的认识

前言爬虫是门很有意思的技术,可以通过爬虫技术获取一些别人拿不到或者需要付费才能拿到的东西,也可以对大量数据进行自动爬取和保存,减少时间和精力去手动做一些累活。可以说很多人学编程,不玩点爬... 查看详情

009数据类型基础

...算机能够识别世间万物,那么我们来想想怎么让计算机来认识我?我们不讲计算机怎么认识我,我们聊聊你是怎么认识我的?如果是你,你是不是会通过我的名字、年龄、身高、性别和爱好认识我。那么可想而知计算机也可以这... 查看详情

用“讲故事”的方式,带你认识python编码问题起源和发展!

...究竟是怎样的呢?我们今天就用“讲故事”的方式,带你认识一下它。2.黄同学给你讲故事1)烽火士兵的故事在正式讲故事之前,我们先来看一下下方这张图,我们暂且称其为《烽火士兵》的故 查看详情

使用 SPI 的 PostgreSQL 并行查询可能吗?

】使用SPI的PostgreSQL并行查询可能吗?【英文标题】:PostgreSQLparallelqueriesusingSPIpossible?【发布时间】:2020-02-1812:05:07【问题描述】:我正在使用PostgreSQL的ServerProgrammingInterface(SPI)来构建我的postgres扩展并执行我的查询。请看this详细... 查看详情

我好想你

你好!不要惊讶,就是我,给你递信的这个人,也许你不认识我,但是你见过我好几次,我也能经常见到你,所以不要觉得很惊讶。我关注你很久了,有时候经常会想你,也许是你太漂亮了吧!好想能认识你,可以吗? 你好... 查看详情

HAL_SPI_Transmit() 会丢弃接收到的数据吗?

】HAL_SPI_Transmit()会丢弃接收到的数据吗?【英文标题】:DoesHAL_SPI_Transmit()discardreceiveddata?【发布时间】:2021-09-0611:16:59【问题描述】:假设我有两个带有全双工SPI连接的STM板(一个是主机,一个是从机),并且假设我在每一端都... 查看详情

laocat带你认识容器与镜像(实践篇二下)(代码片段)

...主要以各容器的挂载和附加命令为主。系列目录LaoCat带你认识容器与镜像(一)LaoCat带你认识容器与镜像(二【一章】)LaoCat带你认识容器与镜像(二【二章】)LaoCat带你认识容器与镜像(二【三章】&... 查看详情