Java SPI

SPI的英文全称为Service Provider Interface, 简单翻译为服务提供者接口, 是jdk提供给“服务提供商”或者“插件开发者”使用的接口.
Java是一门面向对象的语言, 在模块之间一般会采用面向接口的编程的方式, 而在时机编程过程中,api的实现是封装在jar里面的, 当我们想要换一种实现方式的时候, 还需要生成新的jar替换以前实现的类. 使用spi机制就可以直接使用一个新的jar替换旧的实现jar即可.
i.e. 日志模块, xml解析模块, jdbc驱动模块, 这些都在装载模块的时候不具体指明实现类,在使用时给予服务返现的机制动态加载, .

SPI 规范

定义服务的通用接口, 不同的实现厂商/开发者基于通用接口及通具体的实现:

  1. 服务提供者在jar包的META-INF/services/目录下, 新建一个文件为SPI接口“全限定类名”, 文件内容为通用接口的具体实现类的“全限定类名”
  2. 将SPI所在jar放在主程序的classpath中
  3. 服务调用方使用 java.util.ServiceLoader 动态加载具体的实现类到JVM中

SPI经典案例

数据驱动

jdk提供了java.sql.Driver的通用接口, 不同的数据库厂商会提供对应的数据库驱动, 如:mysql, oracle, h2 驱动等, 以h2 为例, 实现类为org.h2.Driver, 在对应的jar下可以看到META-INFO/services目录下名为java.sql.Driver的文件, 其内容为org.h2.Dr
在这里插入图片描述
在我们使用JDBC获取连接的时候, 通常调用DriverManager.getConnection()来获取连接对像, 在DriveManager被加载的时候会调用ServiceLoader动态获取classpath下注册的驱动实现

public class DriverManager {
       
		/**
		 * Load the initial JDBC drivers by checking the System property
		 * jdbc.properties and then use the {@code ServiceLoader} mechanism
		 */
		static {
		    loadInitialDrivers();
		    println("JDBC DriverManager initialized");
		}
		...
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
				...
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                ...
            }
        });
}

思考

当项目中引入多个jdbc驱动的时候(不同的环境使用不同的数据库), 她会具体的使用哪个驱动呢
答案: 在getDriver的时候会根据传入的url 做matche返回对应的驱动, 所以即使在项目导入了多个数据库驱动, 在调用getDriver/getConnection的时候也会根据url 返回正确的数据库Driver

public class DriverManager {
    @CallerSensitive
    public static Driver getDriver(String url)
        throws SQLException {
        // Walk through the loaded registeredDrivers attempting to locate someone
        // who understands the given URL.
        for (DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerClass)) {
               ...
                    if(aDriver.driver.acceptsURL(url)) {// 在这个地方会去匹配对应的url来返回对应Driver
                        // Success!
                        println("getDriver returning " + aDriver.driver.getClass().getName());
                    return (aDriver.driver);
                 ...   
            }

        }
    }

Sl4j

slf4j是典型的日志框架的通用接口, 用的较多的实现有log4j和logback, 当要用logback替换掉log4j的时候只需要引入对应的jar即可, 当然这个需要你代码中使用的都是接口编程. 具体的就不再详细展开了.
在这里插入图片描述

SPI 实例

在这里插入图片描述

number-operate-api

定义通用接口

public interface NumberOperate {
    int operate(int a,int b);
}

numberAdd

依赖number-operate-api, 并做加法实现, 在resources/META-INF/services下创建名称为com.larzhu.numberopearteapi.NumberOperate 的文件, 文件内容为实现类的全限定类名:com.larzhu.numberadd.NumberSubtraction

public class NumberAddService implements NumberOperate {
    @Override
    public int operate(int a, int b) {
        return a+b;
    }
}

numberSubtraction

依赖number-operate-api, 并做减法实现, 在resources/META-INF/services下创建名称为com.larzhu.numberopearteapi.NumberOperate 的文件, 文件内容为实现类的全限定类名:com.larzhu.numberadd.NumberSubtraction

public class NumberSubtraction implements NumberOperate {
    @Override
    public int operate(int a, int b) {
        return a-b;
    }
}

Number-operate-Consumer

具体的调用者, 依赖于通用接口及对应的实现jar, 利用serviceLoader加载spi对应的实现类,

  1. 当引入numberAdd.jar的时候计算结果为“—15”,
  2. 当引入numberSubtraction.jar的时候结果为“—5”,
  3. 当同时引入两个jar, 会输出“—5—15”
public class Test {
    public static void main(String[] args) {
        ServiceLoader<NumberOperate> numberOperates = ServiceLoader.load(NumberOperate.class);
        for (NumberOperate op: numberOperates) {
            int operate = op.operate(10, 5);
            System.out.printf("---"+operate);
        }
    }
}

总结

SPI机制是在java jdk提供的一种将接口与实现分开的一种重要方式, 只提供接口, 具体的实现由子项目/厂商提供,

  1. 提供通用接口
  2. 实现jar在resource/META-INF/services目录下创建以接口全限定类名为名称的文件 , 内容为实现类的全限定类名.
  3. 服务调用时 用java.util.ServiceLoader.load(Interface.class) 来加载具体的实现
  4. 当有多个实现jar被导入时,会将多个实现同时加载, 可以学习jdbc, DriverManger的方式通过url或其他的参数来区分加载具体的某个实现类

Spring, Dubbo都有自己的spi机制, Spring的spi是在META-INF/spring.factories 文件中定义对应的接口与实现, springboot在EnableAutoConfiguration注解的时候import了AutoConfigurationImportSelector ,AutoConfigurationImportSelector里面的 selectImports 会被掉用, 而这个方法中就会利用SpringFacotriesLoader加载META-INF/spring.factories下的所有spi类.
在这里插入图片描述

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值