深入探讨Java SPI机制及其应用场景

目录
  • 一、什么是SPI
  • 二、使用场景
  • 三、使用步骤示例
  • 四、原理解析
    • 1、SPI的核心就是ServiceLoader.load()方法
    • 2、ServiceLoader核心代码介绍

一、什么是SPI

SPI全称Service Provider Interface,是Java提供的一种服务发现机制。实现服务接口和服务实现的解耦。

Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,实现不修改任何代码的情况下切换不同的实现。

二、使用场景

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

如下几个常见的场景:

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

2)Slf4j日志框架

3)Dubbo框架

三、使用步骤示例

假设有个上传附件的场景,可以上传到不同的云储存(如阿里云OSS,亚马逊S3),那么基于Java SPI机制的实现,我们应该做如下步骤:

步骤1、创建4个工程

SPI的核心就是实现服务接口和服务实现的解耦,所以我们不能将接口和实现放在一个工程里面。

  • spi-file-upload,在这定义附件上传接口IFileUpload
  • spi-file-upload-oss,实现附件上传到oss,FileUploadOss实现接口IFileUpload
  • spi-file-upload-s3,实现附件上传到s3,FileUploadS3实现接口IFileUpload
  • spi-file-upload-test,通过ServiceLoader加载接口实现,进行测试

步骤2、 在工程spi-file-upload创建接口IFileUpload

接口代码示例

package com.hj.test.file.oss;
/**
 * 文件上传接口
 */
public interface IFileUpload {
    void upload(String fileName);
}

步骤3、分别创建接口实现类FileUploadOss、FileUploadS3

1)FileUploadOss

在工程的 spi-file-upload-oss 的 resources目录下创建目录META-INF/services,并在该目录中创建以接口IFileUpload全路径命名的文件(com.hj.test.file.IFileUpload),文件内容是接口实现类 com.hj.test.file.oss.FileUploadOss

package com.hj.test.file.oss;
import com.hj.test.file.IFileUpload;
public class FileUploadOss implements IFileUpload {
    @Override
    public void upload(String fileName) {
        System.out.println("上传到阿里云OSS..." + fileName);
    }
}

2)FileUploadS3

在工程的 spi-file-upload-s3 的 resources目录下创建目录META-INF/services,并在该目录中创建以接口IFileUpload全路径命名的文件(com.hj.test.file.IFileUpload),文件内容是接口实现类 com.hj.test.file.s3.FileUploadS3

package com.hj.test.file.s3;
import com.hj.test.file.IFileUpload;
public class FileUploadS3 implements IFileUpload {
    @Override
    public void upload(String fileName) {
        System.out.println("上传到亚马逊s3..." + fileName);
    }
}

步骤4、在工程spi-file-upload-test中创建测试调用类

1)在pom.xml中引入3个依赖工程

<dependency>
    <groupId>com.hj</groupId>
    <artifactId>spi-file-upload</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.hj</groupId>
    <artifactId>spi-file-upload-oss</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.hj</groupId>
    <artifactId>spi-file-upload-s3</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

2)测试实现

package com.hj.test.file.test;
import com.hj.test.file.IFileUpload;
import java.util.Iterator;
import java.util.ServiceLoader;
public class FileTest{
    public static void main(String[] args) {
        ServiceLoader<IFileUpload> loader = ServiceLoader.load(IFileUpload.class);
        for(Iterator<IFileUpload> it = loader.iterator(); it.hasNext();){
            IFileUpload file = it.next();
            file.upload("测试文件上传");
        }
    }
}

控制台输出

上传到阿里云OSS...测试文件上传
上传到亚马逊s3...测试文件上传

如果哪天不想要传到s3,只需要把jar包依赖去掉就可以,无需改代码

四、原理解析

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()初始化

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

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

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

到此这篇关于深入探讨Java SPI机制及其应用场景的文章就介绍到这了,更多相关Java SPI机制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java中的接口及其应用场景解读

    目录 一.接口的特点 二.定义接口 1.interface关键字 2.implements关键字 三.应用场景 1.接口表示规范 2.接口表示能力/行为 四.接口和类之间的关系 五.接口的命名规范 总结 一.接口的特点 1.如果一个抽象类中的所有方法都是抽象的,则可以将这个类定义为Java中的另一种形式——接口.接口是一种特殊的抽象类,接口中只有全局常量和抽象方法,是一种更纯粹的抽象概念. 2.在JDK8中,对接口进行了重新定义,接口中除了有抽象方法外,还可以有默认方法和静态方法:默认方法可以被

  • Java中策略设计模式的实现及应用场景

    目录 介绍 实现 总结 介绍 Java策略模式(Strategy Pattern)是一种行为设计模式,它允许再运行时动态选择算法的行为.策略模式通过将算法封装在可互换的策略对象中,使得客户端代码能够轻松地切换算法,而无需修改原始代码.在策略模式中,每个算法都被实现为一个单独的策略类,这些策略类都实现了相同的接口,从而允许它们在运行时互相替换. 在Java中,可以使用接口或抽象类来定义策略接口.然后对于每个算法,都可以创建一个具体的实现类来实现这个接口.客户端代码可以通过将不同的策略对象传递给上下

  • Java读写锁ReadWriteLock原理与应用场景详解

    Java并发编程提供了读写锁,主要用于读多写少的场景 什么是读写锁? 读写锁并不是JAVA所特有的读写锁(Readers-Writer Lock)顾名思义是一把锁分为两部分:读锁和写锁,其中读锁允许多个线程同时获得,因为读操作本身是线程安全的,而写锁则是互斥锁,不允许多个线程同时获得写锁,并且写操作和读操作也是互斥的. 所谓的读写锁(Readers-Writer Lock),顾名思义就是将一个锁拆分为读锁和写锁两个锁. 其中读锁允许多个线程同时获得,而写锁则是互斥锁,不允许多个线程同时获得写锁,

  • 详解JAVA SPI机制和使用方法

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

  • JAVA SPI机制详解使用方法

    目录 写在前面 什么是SPI 使用场景 实现约定 四种角色 基于JAVA原生特性实现的JAVA SPI机制的DEMO 1. 主要角色 2. 示例代码 3. 说明 基于SPRING BOOT实现的JAVA SPI机制的DEMO 写在前面 Java SPI提供了一种为某个接口寻找服务实现的机制.有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,SPI的核心思想就是解耦. 什么是SPI SPI全称Service Provider Interface,是Java提供的

  • Java SPI 机制知识点总结

    前言 不知大家现在有没有去公司复工,我已经在家办公将近 3 周了,同时也在家呆了一个多月:还好工作并没有受到任何影响,我个人一直觉得远程工作和 IT 行业是非常契合的,这段时间的工作效率甚至比在办公室还高,同时由于我们公司的业务在海外,所以疫情几乎没有造成太多影响. 扯远了,这次主要是想和大家分享一下 Java 的 SPI 机制. 还没看过的朋友的我先做个前景提要,当时的需求: 我实现了一个类似于的 SpringMVC 但却很轻量的 http 框架 cicada,其中当然也需要一个 IOC 容器

  • Java SPI机制原理及代码实例

    SPI的全名为:Service Provider Interface,大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的.在java.util.ServiceLoader的文档里有比较详细的介绍. 简单的总结下 Java SPI 机制的思想.我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块.jdbc模块的方案等.面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码. 一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要

  • Java SPI机制详细介绍

    目录 为什么需要SPI? 什么是SPI?SPI和API的区别 来人,上点对抗 spi-provider spi-user 总结 为什么需要SPI? 思考一个场景,我们封装了一套服务,别人通过引入我们写好的包,就可以使用这些接口API,完成相应的操作,这本来没有什么问题,但是会存在使用该服务的实体有不相同的业务需求,需要进一步的扩展,但是由于api是写好的,想要扩展并非那么的简单,如果存在这样子的场景,我们该怎么办? 可以使用Java 提供的SPI机制 什么是SPI?SPI和API的区别 SPI

  • 详解java实践SPI机制及浅析源码

    1.概念 正式步入今天的核心内容之前,溪源先给大家介绍一下关于SPI机制的相关概念,最后会提供实践源代码. SPI即Service Provider Interface,属于JDK内置的一种动态的服务提供发现机制,可以理解为运行时动态加载接口的实现类.更甚至,大家可以将SPI机制与设计模式中的策略模式建立联系. SPI机制: 从上图中理解SPI机制:标准化接口+策略模式+配置文件: SPI机制核心思想:系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编

  • 深入理解Java中的SPI机制

    本文通过探析JDK提供的,在开源项目中比较常用的Java SPI机制,希望给大家在实际开发实践.学习开源项目提供参考. 1 SPI是什么 SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件. 整体机制图如下: Java SPI 实际上是"基于接口的编程+策略模式+配置文件"组合实现的动态加载机制. 系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接

  • Java Spring Dubbo三种SPI机制的区别

    目录 前言 SPI 有什么用?​ JDK SPI​ Dubbo SPI Spring SPI​ 对比​ 前言 SPI 全称为 Service Provider Interface,是一种服务发现机制.SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类.这样可以在运行时,动态为接口替换实现类.正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能. 本文主要是特性 & 用法介绍,不涉及源码解析(源码都很简单,相信你一定一看就懂) SPI 有什

  • 一文带你了解Java中的SPI机制

    目录 1: SPI机制简介 2: SPI原理 3: 使用场景 4: 源码论证 5: 实战 6: 优缺点 6.1 优点 6.2 缺点 1: SPI机制简介 SPI 全称是 Service Provider Interface,是一种 JDK 内置的动态加载实现扩展点的机制,通过 SPI 技术我们可以动态获取接口的实现类,不用自己来创建.这个不是什么特别的技术,只是 一种设计理念. 2: SPI原理 Java SPI 实际上是基于接口的编程+策略模式+配置文件组合实现的动态加载机制. 系统设计的各个

  • Java进阶之SPI机制详解

    一.前言 SPI的英文全称为Service Provider Interface,字面意思为服务提供者接口,它是jdk提供给"服务提供厂商"或者"插件开发者"使用的接口. 在面向对象的设计中,模块之间我们一般会采取面向接口编程的方式,而在实际编程过程过程中,API的实现是封装在jar中,当我们想要换一种实现方法时,还要生成新的jar替换以前的实现类.而通过jdk的SPI机制就可以实现,首先不需要修改原来作为接口的jar的情况下,将原来实现的那个jar替换为另外一种实

随机推荐