好记性不如铅笔头

java, 编程

【转】Java-SPI机制使用示例及原理浅析

本文转自【【Java杂记】SPI机制:使用示例及原理浅析 – 掘金 (juejin.cn)】,有大量删改。

CONTENTS

SPI是什么

SPI全称Service Provider Interface,是 JDK 内置的一种服务提供发现机制,目前市面上有很多框架都是用它来做服务的扩展发现。简单来说,它是一种动态替换发现的机制。它可以用来启用框架扩展和替换组件。整体机制图如下: 在这里插入图片描述

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

  • 接口:调用方需要的服务
  • 策略模式:多个提供方,会提供自己的接口实现
  • 配置文件:提供方告知调用方,具体实现类在哪里,然后调用方去加载(本质上是一种通信方式)

所以SPI的核心思想就是解耦

使用示例

要使用Java SPI,应遵循以下流程:

在这里插入图片描述

  1. 为什么配置文件是要放在META-INF/services? 原因是ServiceLoader默认是加载该目录下文件 在这里插入图片描述
  2. ServiceLoader有什么用?当接口实现类所在的jar包放在主程序的classpath中,ServiceLoder会根据配置文件中的全类名将实现类加载到JVM中

再注意一点,SPI的实现类必须携带一个不带参数的构造方法(默认空参构造)

示例代码

1.定义一组接口 (假设是com.example.spitestDemo.SpiTestDemo),表示服务调用方

// 服务调用方需要的服务
public interface SpiTestDemo {
    void way1();
    void way2();
}

2.新建两个项目,分别实现该接口(引入接口jar包),表示两个服务提供方

// 提供方1的实现
public class SpiTestDemoImpl1 implements SpiTestDemo {
    @Override
    public void way1() {
        System.out.println("SpiTestDemoImpl1------way1");
    }
 
    @Override
    public void way2() {
        System.out.println("SpiTestDemoImpl1------way2");
    }
}
 
// 提供方2的实现 
public class SpiTestDemoImpl2 implements SpiTestDemo {
    @Override
    public void way1() {
        System.out.println("SpiTestDemoImpl2------way1");
    }
 
    @Override
    public void way2() {
        System.out.println("SpiTestDemoImpl2------way2");
    }
}

3.在两个提供方的src/main/resources/ 下建立 /META-INF/services 目录,新增一个以接口命名的文件 (com.example.spitestDemo.SpiTestDemo文件)

- src
    -main
        -resources
            - META-INF
                - services
                    - com.example.spitestDemo.SpiTestDemo

 

内容是要应用的实现类全类名(这里是SpiTestDemoImpl1和SpiTestDemoImpl2,每行一个类)

com.example.spitestdemo.SpiTestDemoImpl1
com.example.spitestdemo.SpiTestDemoImpl2

4.调用方引入提供方的两个jar包,使用 ServiceLoader 来加载配置文件中指定的实现。

class SpitestdemoApplicationTests {
 
    public void testWay(){
    	 // 传入接口作为参数,返回解析到的实现类
        ServiceLoader<SpiTestDemo> serviceLoader = ServiceLoader.load(SpiTestDemo.class);
        // 不同的实现类,有不同的逻辑
        for(SpiTestDemo spiTestDemo : serviceLoader){
            spiTestDemo.way1();
            spiTestDemo.way2();
        }
    }
}

在这里插入图片描述

总结

优点:使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。

缺点

  • JDK 标准的 SPI 会一次性加载实例化扩展点的所有实现,就是如果你在 META-INF/service 下的文件里面加了 N 个实现类,那么 JDK 启动的时候都会一次性全部加载。那么如果有的扩展点实现初始化很耗时或者如果有些实现类并没有用到, 那么会很浪费资源
  • 如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位到是这个原因
  • 多个并发多线程使用ServiceLoader类的实例是不安全的。

发表评论

16 + 6 =

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据