Java进阶之SPI机制详解

 更新时间:2021年05月17日 11:46:46   作者:听到微笑  
Java SPI机制在很多大型中间建码,例如Dubbo中均有采用,属于高级Java开发的进阶必备知识点,务必要求掌握.文中有非常详细的代码示例及解释,需要的朋友可以参考下

一、前言

SPI的英文全称为Service Provider Interface,字面意思为服务提供者接口,它是jdk提供给“服务提供厂商”或者“插件开发者”使用的接口。

在面向对象的设计中,模块之间我们一般会采取面向接口编程的方式,而在实际编程过程过程中,API的实现是封装在jar中,当我们想要换一种实现方法时,还要生成新的jar替换以前的实现类。而通过jdk的SPI机制就可以实现,首先不需要修改原来作为接口的jar的情况下,将原来实现的那个jar替换为另外一种实现的jar即可。

总结一下SPI的思想:在系统的各个模块中,往往有不同的实现方案,例如日志模块的方案、xml解析的方案等,为了在装载模块的时候不具体指明实现类,我们需要一种服务发现机制,java spi就提供这样一种机制。有点类似于IoC的思想,将服务装配的控制权移到程序之外,在模块化设计时尤其重要。

顺便提一下,Java SPI机制在很多大型中间件吗,例如Dubbo中均有采用,属于高级Java开发的进阶必备知识点,务必要求掌握。

二、SPI规范

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

1.在jar包(服务提供者)的META-INF/services/目录中,新建一个文件,文件名为SPI接口的"全限定名"。 文件内容为该接口的具体实现类的"全限定名"

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

3.服务调用方使用java.util.ServiceLoader去动态加载具体的实现类到JVM中

三、SPI应用案例

3.1 数据库驱动

java.sql.Driver的spi实现,有mysql驱动、oracle驱动等。以mysql为例,实现类是com.mysql.jdbc.Driver,在mysql-connector-java-5.1.6.jar中,我们可以看到有一个META-INF/services目录,目录下有一个文件名为java.sql.Driver的文件,其中的内容是com.mysql.jdbc.Driver。

在我们使用JDBC获取连接时,我们通常会调用DriverManager.getConnection()方法获取连接对象,而在Driver类初始化时会使用ServiceLoader动态获取classpath下“注册”的驱动实现:

static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
}

private static void loadInitialDrivers() {
        .....
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
				//使用ServiceLoader动态加载具体的驱动实现类
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });
		.....
    }

3.2 Slf4j

slf4j是一个典型的门面接口,早起我们使用log4j作为日记记录框架,我们需要同时引入slf4j和log4j的依赖。后面比较流行logback,我们也想要把项目切换到logback上来,此时利用SPI的机制,我们只需要把log4j的jar包替换为logback的jar包就可以了。

log4j-to-slf4j.jar中我们可以看到前面提到的服务提供方的SPI接口声明

这样我们只需要将log4j-to-slf4j.jar引入classpath,slf4j就能够获取到org.apache.logging.slf4j.SLF4JProvider作为实现类。

四、SPI示例

模块依赖关系:

4.1 spi-operate-service模块

spi-operate-service中定义INumOperate接口:

package cn.bigcoder.spi.operate.service;

/**
 * @author: Jindong.Tian
 * @date: 2020-11-29
 * @description:
 **/
public interface INumOperate {

    int exec(int num1, int num2);
}

4.2 spi-operate-add模块

spi-operate-add中定义加法实现:

package cn.bigcoder.spi.operate.add;


import cn.bigcoder.spi.operate.service.INumOperate;

/**
 * @author: Jindong.Tian
 * @date: 2020-11-29
 * @description:
 **/
public class NumAddOperateImpl implements INumOperate {

    public int exec(int num1, int num2) {
        return num1 + num2;
    }
}

resource/METAINF/resoures目录下创建cn.bigcoder.spi.operate.service.INumOperate文件:

cn.bigcoder.spi.operate.add.NumAddOperateImpl

4.3 spi-operate-multiplication模块

spi-operate-multiplication模块中定义乘法的实现:

package cn.bigcoder.spi.operate.multiplication;

import cn.bigcoder.spi.operate.service.INumOperate;

/**
 * @author: Jindong.Tian
 * @date: 2020-11-29
 * @description:
 **/
public class NumMultiOperateImpl implements INumOperate {
    
    public int exec(int num1, int num2) {
        return num1 * num2;
    }
}

同样的在resource/METAINF/resoures目录下创建cn.bigcoder.spi.operate.service.INumOperate文件:

cn.bigcoder.spi.operate.multiplication.NumMultiOperateImpl

4.4 spi-operate-consumer模块

spi-operate-consumer模块中,我们编写测试类获取classpath中INumOperate实现类:

package cn.bigcoder.spi.operate;

import cn.bigcoder.spi.operate.service.INumOperate;
import org.junit.Test;

import java.util.Iterator;
import java.util.ServiceLoader;

/**
 * @author: Jindong.Tian
 * @date: 2020-11-29
 * @description:
 **/
public class INumOperateTest {
    @Test
    public void test() {
        // SPI机制,寻找所有的实现类,顺序执行
        ServiceLoader<INumOperate> loader = ServiceLoader.load(INumOperate.class);
        Iterator<INumOperate> iterator = loader.iterator();
        if (iterator.hasNext()) {
            INumOperate numOperate = iterator.next();
            System.out.println(numOperate.exec(1, 2));
        } else {
            throw new RuntimeException("classpath中未找到cn.bigcoder.spi.operate.INumOperate实现类");
        }
    }
}

此时如果我们在spi-operate-consumer中引入spi-operate-add,则测试方法执行求和操作;如果引入spi-operate-multiplication,则测试方法执行乘法操作。

到此这篇关于Java进阶之Java SPI详解的文章就介绍到这了,更多相关Java SPI内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot通知机制的实现方式

    SpringBoot通知机制的实现方式

    这篇文章主要介绍了SpringBoot通知机制的实现方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • java swing框架实现贪吃蛇游戏

    java swing框架实现贪吃蛇游戏

    这篇文章主要为大家详细介绍了java swing框架实现贪吃蛇游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-12-12
  • 使用jpa的时候set实体类属性自动持久化的解决方案

    使用jpa的时候set实体类属性自动持久化的解决方案

    这篇文章主要介绍了使用jpa的时候set实体类属性自动持久化的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • AbstractQueuedSynchronizer(AQS)锁状态同步和排队管理

    AbstractQueuedSynchronizer(AQS)锁状态同步和排队管理

    这篇文章主要介绍了为大家AbstractQueuedSynchronizer(AQS)锁状态同步和排队管理源码解读,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • java实现简单的爬虫之今日头条

    java实现简单的爬虫之今日头条

    最近在学习搜索方面的东西,需要了解网络爬虫方面的知识,虽然有很多开源的强大的爬虫,但本着学习的态度,想到之前在做资讯站的时候需要用到爬虫来获取一些文章,今天刚好有空就研究了一下.在网上看到了一个demo,使用的是Jsoup,我拿过来修改了一下,有需要的朋友可以参考
    2016-11-11
  • Flowable历史查询实例分析

    Flowable历史查询实例分析

    这篇文章主要介绍了Flowable历史查询实例分析,历史是记录流程执行过程中发生的事情,并将其永久存储的组件,与运行时数据不同,历史数据在流程实例完成以后仍保存在数据库中,下面我们来深入了解
    2023-10-10
  • Java多线程的具体介绍与使用笔记小结

    Java多线程的具体介绍与使用笔记小结

    Java多线程详细介绍线程是程序执行的最小单元,多线程是指程序同一时间可以有多个执行单元运行(这个与你的CPU核心有关)。 接下来通过本文给大家介绍Java多线程的具体介绍与使用笔记小结,感兴趣的朋友一起看看吧
    2021-05-05
  • Spring Aware标记接口使用案例解析

    Spring Aware标记接口使用案例解析

    这篇文章主要介绍了Spring Aware标记接口使用案例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • 解决DataInputStream read不等于-1,socket文件传输只能传输一个文件无法传输多个问题

    解决DataInputStream read不等于-1,socket文件传输只能传输一个文件无法传输多个问题

    这篇文章主要介绍了解决DataInputStream read不等于-1,socket文件传输只能传输一个文件无法传输多个问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • Mybatis-plus自动填充不生效或自动填充数据为null原因及解决方案

    Mybatis-plus自动填充不生效或自动填充数据为null原因及解决方案

    本文主要介绍了Mybatis-plus自动填充不生效或自动填充数据为null原因及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05

最新评论