JAVA中的SPI机制

深入理解SPI机制

一. 什么是SPI

SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的 META-INF/services文件夹查找文件,自动加载文件里所定义的类。

这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制。我们先通过一个很简单的例子来看下它是怎么用的.

小例子

首先,我们需要定义一个接口,SPIService

public interface SPIService {
    void execute();
}

然后,定义两个实现类

public class SpiImpl1 implements SPIService{
    public void execute() {
        System.out.println("SpiImpl1.execute()");
    }
}

public class SpiImpl2 implements SPIService{
    public void execute() {
        System.out.println("SpiImpl2.execute()");
    }
}

最后呢,要在ClassPath路径下配置添加一个文件。文件名字是接口的全限定类名,内容是实现类的全限定类名,多个实现类用换行符分隔。

文件路径如下:
在这里插入图片描述

内容就是实现类的全限定类名:

cloud.tianai.rpc.demo.SpiImpl1
cloud.tianai.rpc.demo.SpiImpl2

使用JAVA自带的类查找 SPI 扩展

然后我们就可以通过ServiceLoader.load或者Service.providers方法拿到实现类的实例。其中,Service.providers包位于sun.misc.Service,而ServiceLoader.load包位于java.util.ServiceLoader

public static void main(String[] args) {    
    // 方式 1
    Iterator<SPIService> providers = Service.providers(SPIService.class);
    while(providers.hasNext()) {
        SPIService ser = providers.next();
        ser.execute();
    }


    // 方式 2
    ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class);
    Iterator<SPIService> iterator = load.iterator();
    while(iterator.hasNext()) {
        SPIService ser = iterator.next();
        ser.execute();
    }
}

DUBBO 的 SPI机制

Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。

Dubbo 改进了 JDK 标准的 SPI 的以下问题:

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
  • 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

约定:

在扩展类的 jar 包内 [1],放置扩展点配置文件 META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。

示例:

​ 首先,我们定义一个接口,名称为 Robot。

public interface Robot {
    void sayHello();
}

接下来定义两个实现类,分别为 OptimusPrime 和 Bumblebee。

public class OptimusPrime implements Robot {
    
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Optimus Prime.");
    }
}

public class Bumblebee implements Robot {

    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee.");
    }
}

接下来 META-INF/dubbo文件夹下创建一个文件,名称为 Robot 的全限定名 org.apache.spi.Robot。文件内容为实现类的全限定的类名,如下:
在这里插入图片描述

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

做好所需的准备工作,接下来编写代码进行测试。

public class DubboSPITest {

    @Test
    public void sayHello() throws Exception {
        ExtensionLoader<Robot> extensionLoader = 
            ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        bumblebee.sayHello();
    }
}

dubbo中的SPI源码分析 参考 http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html

总结:

  • 合理使用 SPI 机制可以大大降低项目的耦合度,

  • 这一功能对第三方架构定做非常有用,

  • 在自身的 业务中台 或其他架构中使用SPI 后扩展会非常方便

  • 著名框架 SpringBoot的自动化部署也是实现了一个类似于 SPI 架构的功能才得以无感知扩展

----------------------------------------- 广告时间 -----------------------------------------

各位看官, 欢迎关注公众号,每天推送有意思的小东西哦!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值