java SPI机制的使用及原理

java SPI机制的使用及原理

本片文章是针对dubbo SPI机制深入分析的平滑过渡的作用。当然咱们主要是学习优秀的思想,SPI就是一种解耦非常优秀的思想,我们可以思考在我们项目开发中是否可以使用、是否可以帮助我们解决某些问题、或者能够更加提升项目的框架等

一、SPI是什么

SPI(service provider interface)是java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。

如果用上面这句话来描述SPI那么是一点卵用没有,下面用生动的例子来阐述。其实SPI跟我们的策略设计模式比较相似,如果对策略设计模式不太了解的,可以先花点时间去学习一下。

实例:假如,我们在京东上购买商品需要付款,假如我们可以选择的支付的模块有支付宝、微信、银行卡。如果我们使用策略设计模式的话,简单的代码如下。

/*** * 抽象支付 */
public interface Pay {
   

    void pay();
}
/** * @Auther: * @Date: 2020/5/2 14:28 * @Description: 支付宝付款 */
public class AliPay implements Pay {
   
    @Override
    public void pay() {
   
        System.out.println("使用支付宝pay....");
    }
}
/** * @Auther: * @Date: 2020/5/2 14:28 * @Description: 微信pay */
public class WechatPay implements Pay {
   
    @Override
    public void pay() {
   
        System.out.println("使用微信支付....");
    }
}
/** * @Auther: * @Date: 2020/5/2 14:29 * @Description:银行卡pay */
public class BankCardPay implements Pay {
   
    @Override
    public void pay() {
   
        System.out.println("使用银行卡支付....");
    }
}

你可以根据你的需求创建出相应的Pay的实现类,然后调用pay(),例如我简写一下

/**
 * @Auther:
 * @Date: 2020/5/2 14:36
 * @Description:
 */
public class Context {


    private final Pay pay;

    public Context(Pay pay) {
        this.pay = pay;
    }

    public void invokeStrategy(){
        pay.pay();
    }
}
/** * @Auther: * @Date: 2020/5/2 14:35 * @Description: */
public class PayTest {
   

    public static void main(String[] args) {
   
        //ali
        Context aliContext = new Context(new AliPay());
        aliContext.invokeStrategy();
        //wechat
        Context wechatContext = new Context(new WechatPay());
        wechatContext.invokeStrategy();
    }
}

从上面的代码中我们其实可以看到还是需要我们显示的创建出相应的支付模块。

二、SPI如何使用

那么现在有这样的场景:当我的项目里面有什么支付模块我就使用什么样的支付模块,比如说有支付宝支付模块就选择支付宝、有微信支付模块我就选择微信支付、同时有多个的时候,我默认选择第一个,此时我们就可以使用SPI,先看下如何使用。

1、创建META-INF/services文件夹,然后创建一个以Pay接口全限定名为名字的文件
在这里插入图片描述

2、在文件中编写想要实现哪个Pay的实现类(AliPay,WechatPay,BankCardPay),注意也要是全限定名

假如是是支付宝支付的模块,上面文件的内容:

com.taolong.dubbo.spi.strategy.AliPay

3、获取Pay并调用

获取并调用的逻辑,我就修改下上面的策略模式中的Context的invokerStrategy方法,这里假设默认使用第一个

public void invokeStrategy(){
   
    ServiceLoader<Pay> payServiceLoader = ServiceLoader.load(Pay.class);
    Iterator<Pay> iterator = payServiceLoader.iterator();
    if (iterator.hasNext()){
   
        iterator.next().pay();
    }
}

main方法调用

 public static void main(String[] args) {
   
// //ali
// Context aliContext = new Context(new AliPay());
// aliContext.invokeStrategy();
// //wechat
// Context wechatContext = new Context(new WechatPay());
// wechatContext.invokeStrategy();
        Context context = new Context();
        context.invokeStrategy();
    }

上面就是使用的SPI机制,让其选择一个Pay的实现类,这样子就比较灵活了,比如正常的团队工作情况是下面这样子。

A团队:负责支付宝支付支付模块的开发

B团队:负责微信支付模块的开发

C团队:负责银行卡支付模块的开发

此时A团队的支付模块里面只需要新建一个“com.taolong.dubbo.spi.strategy.Pay”的文件,文件的内容对应Alipay(他们自己命名),然后编写自己的支付逻辑即可

B团队也只需要新建“com.taolong.dubbo.spi.strategy.Pay”文件的内容比如是WechatPay(他们自己命名),然后编写自己的逻辑即可。

相当于Pay定义了一个规范,不管是微信、还是支付宝支付只要符合这个规范就行。在使用的使用,我们只需要加入相应的依赖(比如支付宝模块的pom依赖,微信模块的pom依赖),项目就能自动发现具体的实现类,然后调用相应的模块的支付方法。

三、SPI的优秀实现案例

如果对我上面的描述不太理解的话,我们来看一个真实的使用上述SPI的例子—数据库驱动(Driver)

我们知道,当我们的项目里面使用引用了mysql的驱动pom依赖时,我们的项目里面会自动选择使用mysql的驱动,我们甚至不需要手动去加载。我们来看看它的具体实现。

1、首先看一下java.sql.Driver的类

这里面也是相当于定义了一个规范

2、其次看mysql驱动包的META-INF/services文件夹下面有没有指定的文件

在这里插入图片描述
很熟悉,命名就是java.sql.Driver

3、打开文件查看一下文件内容

在这里插入图片描述
这里面就能看到我们的mysql的驱动了,到这里基本上就确认这也是使用SPI实现的,顺便说一下,现在为什么我们不需要使用Class.forName()去加载驱动了,这是因为DriverManager使用SPI的机制已经帮我们加载好了,我们来看看DriverManager的类

(1)静态代码块

/** * 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");
}

oadInitialDrivers()

private static void loadInitialDrivers() {

String drivers;
try {

drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {

public String run() {

return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {

drivers = null;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction<Void>() {

public Void run() {

//重点看这里
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
/* Load these drivers, so that they can be instantiated. * It may be the case that the driver class may not be there * i.e. there may be a packaged driver with the service class * as implementation of java.sql.Driver but the actual class * may be missing. In that case a java.util.ServiceConfigurationError * will be thrown at runtime by the VM trying to locate * and load the service. * * Adding a try catch block to catch those runtime errors * if driver not available in classpath but it's * packaged as service and that service is there in classpath. */
try{

while(driversIterator.hasNext()) {

driversIterator.next();
}
} catch(Throwable t) {

// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {

return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {

try {

println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {

println("DriverManager.Initialize: load failed: " + ex);
}
}
}

很明显就能看到它的调用方法跟我们上面将的例子是一样的,唯一不同的是它会加载所有的驱动。

不仅仅是数据库驱动使用了SPI,还有slf4j、spring等等

上面的java的SPI的源码本文限于篇幅,就不讲解了,感兴趣的可以自行阅读,不过,我们也能够猜测出它的主要的逻辑,下面用一副简单的图来描述一下

在这里插入图片描述
不管是文件名还是文件内容都是全限定名,所以通过反射很容易创建相应的类

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/111211.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • ubuntu18.04 linux journalctl 命令

    ubuntu18.04 linux journalctl 命令目录Help 输出所有的日志记录 匹配(match) 把日志保存到文件中 限定日志所能占用的最高容量 查看某次启动后的日志 查看指定时间段的日志 同时应用match和时间过滤条件 按unit过滤日志 通过日志级别进行过滤 实时更新日志 只显示最新的n行 控制输出 按可执行文件的路径过滤 查看内核日志 总结journalctl用来查询systemd…

  • DEDECMS中的几个常见的自定义常量DEDEMEMBER等位置

    DEDECMS中的几个常见的自定义常量DEDEMEMBER等位置

  • xcode编辑xib文件无限卡与编译错误解决

    xcode编辑xib文件无限卡与编译错误解决

  • Linux dpkg 命令

    Linux dpkg 命令Linuxdpkg命令note:软件测试实习笔记1dpkgdpkg-idpkg-rdpkg-Pdpkg-ldpkgdpkg是linux系统下用来安装、创建和管理软件包的工具。其安装的软件包一般是下载到本地的软件包,拓展名是deb。格式:dpkg参数package.deb(初学者容易忘记打空格,在命令和参数以及参数和软件包中间要打空格,否则linux会无法

  • Linux正确删除软连接[通俗易懂]

    在Linux上删除软连接不要使用rm-rf!!!在Linux上删除软连接不要使用rm-rf!!!在Linux上删除软连接不要使用rm-rf!!!因为如果使用rm-rflinkName的方式,如果不小心在目录后面加了“/”,或者按了Tab键补全,执行之后会删除源目录文件。如果要使用rm-rflinkName的时候一定要注意源、目标文件或目录都不要在后面加…

  • windows安装wget命令_wget怎么用

    windows安装wget命令_wget怎么用在linux操作系统中,我们会经常要用到wget下载文件。wget非常稳定,它在带宽很窄的情况下和不稳定网络中有很强的适应性。在linux中使用wget时,若报-bash:wget:commandnotfound,则表明没有安装wget,需要安装,安装命令如下:yum-yinstallwget安装完成即可以使用。…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号