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)


相关推荐

  • (转)算法帝国:华尔街交易怪兽的核武器缔造史

    (转)算法帝国:华尔街交易怪兽的核武器缔造史算法帝国:华尔街交易怪兽的核武器缔造史华尔街见闻2017-02-01访问量5701980年华尔街的黑客生涯:天时地利http://wallstreetcn.com/node/28758320世纪70年代末期,算法开始进入人们的工作,这一趋势席卷了世界各地的金融市场,标志着华尔街黑客时代已然来临。华尔街逐渐吸引了美国越来越多杰出的数学家和科学家投身于编写交易算法的工作。在布莱克?

  • 跳出循环语句

    跳出循环语句跳出循环语句

  • group by 的用法[通俗易懂]

    group by 的用法[通俗易懂]版权声明:本文为CSDN博主「IT界一股清流」的原创文章,遵循CC4.0BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/jerrytomc

  • CSS之创意hover效果

    CSS之创意hover效果

    2020年11月19日
  • fromopen函数通达信_precision函数

    fromopen函数通达信_precision函数apply及applyIf方法都是用于实现把一个对象中的属性应用于另外一个对象中,相当于属性拷贝。  不同的是apply将会覆盖目标对象中的属性,而applyIf只拷贝目标对象中没有而源对象中有的属性。 apply方法的签名为“apply(Objectobj,Objectconfig,Objectdefaults):Object”, 该方法包含三个参数…

  • map:根据 value 找 key ?

    map:根据 value 找 key ?在之前的学习中,我们在使用map的时候,都是利用key找value。之前我们使用的函数是find,若存在,返回查找到的指向第一个key的迭代器,若不存在,返回尾后迭代器。反过头来想一想,我们可不可以根据value找key呢?答案是肯定的。我们使用find_if+lambda可以实现。返回值和find一致。实例1:std::strings="c";autofin…

发表回复

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

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