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

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

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

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 计算机操作系统学习笔记 第一章、操作系统概论

    计算机操作系统学习笔记 第一章、操作系统概论详细介绍了计算机系统概论,带大家入门计算机操作系统

    2022年6月28日
    27
  • mysql timestampdiff>_MySQL TIMESTAMPDIFF()用法及代码示例

    mysql timestampdiff>_MySQL TIMESTAMPDIFF()用法及代码示例TIMESTAMPDIFF():MySQL中的此函数用于从另一个函数中减去DateTime表达式后返回一个值。用法:TIMESTAMPDIFF(unit,expr1,expr2)Parameters:它将接受三个参数。单位-它表示结果的单位。可以是以下之一。微秒,秒,分钟,小时,天,周,月,季度,年expr1-第一个日期或DateTime表达式。expr2-第二个日期或DateTime表达式。返回…

    2022年6月5日
    32
  • PyCharm汉化之后,点击设置没反应,完美解决方法[通俗易懂]

    PyCharm汉化之后,点击设置没反应,完美解决方法[通俗易懂]请先检查下pycharm的安装目录lib下是不是有中文汉化包resources_cn.jar有过有的话建议:1.更换一个汉化包或者将原来的resources_en.jar也放进lib目录下2.将中文汉化包resources_cn.jar删除,只留下原版的resources_en.jar…

    2022年5月9日
    334
  • 想要复制网页的文字网页不让复制_如何复制文字

    想要复制网页的文字网页不让复制_如何复制文字作者:iamlaosong当我们需要复制网页上的内容时,往往会碰到不能复制的情况,面对这个问题,不同的情况有不同的应对方法,比如禁止JavaScript运行,查看源代码,另存为网页文件等。这些方法也可以用,现在有个更通用的办法是QQ屏幕截图所带的功能,不管网页用的什么技术,能看见就可以复制,特别适合不太懂技术的人。要用QQ截图功能,QQ肯定是要登录的,然后用浏览器打开需要复制文字的网页,按QQ屏幕截图快捷键Ctrl+Alt+A选择需要复制文字的区域,在弹出的菜单中点击“翻译”或者“屏幕识图”两个按钮

    2022年10月12日
    0
  • Java设计模式(四)之创建型模式:建造者模式

    Java设计模式(四)之创建型模式:建造者模式

    2021年4月8日
    144
  • 离心泵CAE_2_ICEM剖分网格_2_叶轮流道[通俗易懂]

    离心泵CAE_2_ICEM剖分网格_2_叶轮流道[通俗易懂]针对本科毕设中所涉及到的离心泵数值分析和性能计算,将用最简单粗暴的方法,讲解如何基于CFturbo、ICEM、FLUENT来开展离心泵水力设计和性能分析的计算机辅助(CAE)实现。离心泵的水力设计由CFturbo软件实现;网格剖分由ICEM软件实现;CFD数值计算由FLUENT软件实现;并验证设计值是否达到。这里是第二部分,ICEM软件实现离心泵过流通道的网格剖分,含叶轮流道、进口延伸段、蜗壳流道的网格剖分。由于三个流道分开来划分网格,所以分三部分来分别讲解,这里是第2篇,叶轮流道的网格剖分……

    2022年5月26日
    47

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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