SPI的简单介绍

1:SPI是什么

在这里插入图片描述
首先,SPI是jdk制定的一种规范 ,满足服务使用者在符合开闭原则基础上来编写代码,而能能够实现使用不同的服务提供方提供的服务,既然是规范肯定就需要有接口,而后还要有接口的具体实现,以及具体实现如何暴露给使用者,最后使用者如何使用这些具体实现,都会在SPI中进行制定。如数据库驱动,日志等,有些抽象,接下里使用一个实例来进行说明其具体的使用方法。

2:实例-搭建项目

本例子会采用maven父子项目方式创建5个项目(模块),分别如下:

1:spiparent,作为父项目。
2:spiinterface,指定spi规范的接口,简单定义一个sayHi的方法。
3:spiconcrete-english,实现spiinterface中的接口,实现方式为使用英语sayHi。
4:spiconcrete-chinese,实现spiinterface的接口,实现方式为使用汉语sayHi。
5:spi-invoker,spiconcrete-english和spiconcrete-chinese的具体使用者。

先看最终项目创建完毕结构:
在这里插入图片描述

2.1:创建spiparent

  • new->project
    在这里插入图片描述
  • 选择maven,不勾选Create from archetype
    在这里插入图片描述
  • 输入GAV信息
    在这里插入图片描述
  • next,输入项目名称
    一般和Artifactid保持一致,因此可不做修改。
    在这里插入图片描述
  • finish
    在这里插入图片描述
  • 删除src
    因为作为父项目存在,所以只需要保留pom.xml,不需要源代码文件和资源文件等,因此直接删除src
    在这里插入图片描述

2.2:创建spiinterface

  • 鼠标选中spiparent
  • new->module
    在这里插入图片描述
  • 选择maven,不选择Create from archetype
    在这里插入图片描述
  • 输入ArtifactId为spiinterface
    在这里插入图片描述
  • next->finish
    在这里插入图片描述

2.3:创建spiconcrete-english

2.2,只不过ArtifactId=spiconcrete-english,设置完如下:
在这里插入图片描述

2.4:创建spiconcrete-chinese

2.2,只不过ArtifactId=spiconcrete-chinese,设置完如下:
在这里插入图片描述

2.5:创建spi-invoker

2.2,只不过ArtifactId=spi-invoker,设置完如下:
在这里插入图片描述

3:实例-写代码

3.1:在spiinterface定义接口

public interface SayHi {
    void sayHi();
}

3.2:在spiconcrete-chinese实现接口

  • 引入依赖项目spiinterface
  <dependencies>
    <dependency>
      <groupId>dongshi.daddy</groupId>
      <artifactId>spiinterface</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
  • 实现接口
public class ChineseSayHi implements SayHi {

    public void sayHi() {
        System.out.println("你好,欢迎来到叶寨村!!!");
    }
}
  • 定义services
    做法为在资源文件夹下创建META-INF/services文件夹,然后创建名称为SPI接口全限定名的文件,并将ChineseSayHi全限定名写到文件中,如下:
    在这里插入图片描述
dongshi.daddy.ChineseSayHi

3.3:在spiconcrete-english实现接口

  • 引入依赖项目spiinterface
  <dependencies>
    <dependency>
      <groupId>dongshi.daddy</groupId>
      <artifactId>spiinterface</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
  • 实现接口
public class EnglishSayHi implements SayHi {

    public void sayHi() {
        System.out.println("welcome to yezhaicun country!!!");
    }
}
  • 定义services
    做法为在资源文件夹下创建META-INF/services文件夹,然后创建名称为SPI接口全限定名的文件,并将EnglishSayHi全限定名写到文件中,如下:
    在这里插入图片描述
dongshi.daddy.EnglishSayHi

4:实例-测试

具体使用会用到SPI规范中提供的工具类java.util.ServiceLoader,该工具类专门用来从classpath下的META-INF/services下加载对应的具体服务的实现。

4.1:在spi-invoker编写测试类

  • 引入依赖(先使用spiconcrete-chinese
  <dependencies>
    <dependency>
      <groupId>dongshi.daddy</groupId>
      <artifactId>spiinterface</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>dongshi.daddy</groupId>
      <artifactId>spiconcrete-chinese</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
  • 测试代码
public class SayHiTestMain {

    public static void main(String[] args) {
        ServiceLoader<SayHi> load = ServiceLoader.load(SayHi.class);
        for (SayHi sayHi : load) {
            sayHi.sayHi();
        }
    }
}

  • 运行
你好,欢迎来到叶寨村!!!

如果此时我们想要使用英语来sayHi,怎么办呢?只需要将spiconcrete-chinese换为spiconcrete-english的依赖即可,如下:

  <dependencies>
    <dependency>
      <groupId>dongshi.daddy</groupId>
      <artifactId>spiinterface</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>dongshi.daddy</groupId>
      <artifactId>spiconcrete-english</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
  • 再次运行
welcome to yezhaicun country!!!

满足了开闭原则

5:源码

这里

6:应用实例

6.1:在数据库驱动中应用

数据库在计算机领域具有非常重要的地位,因此对于数据相关的驱动则无疑是更加重要的,因为我们使用操作数据库必须依赖于这些驱动程序,因此驱动领域中的规范就显的尤为的重要了,对于此,jdk大佬们基于SPI的规范定义了数据库驱动的规范接口,该接口是java.sql.Driver,具体定义如下:

package java.sql;
public interface Driver {
}

接下来以mysql驱动程序为例进行说明。如下是MySQL数据库驱动包截图,可以看到里面定义了META-INF/services/java.sql.Driver文件,内容为该类的具体实现类:
在这里插入图片描述

com.mysql.jdbc.Driver

com.mysql.jdbc.Driver类定义如下:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

可以看到其实现了数据库驱动SPI规范接口java.sql.Driver

6.2:在日志框架中的应用

为了统一日志框架,方便用户灵活切换各种不同的日志框架,apache公司定义了日志门面slf4j,然后通过SPI实现以插槽式的方式来接入具体的日志实现,如下我们使用log4j12的日志实现,pom 如下:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.3</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>2.0.3</version>
</dependency>

slf4j-api中的SPI接口如下:
在这里插入图片描述
slf4j-log4j12服务实现配置如下:
在这里插入图片描述
这样,当我们执行代码private Logger logger = org.slf4j.LoggerFactory.getLogger(T.class);获取具体的Logger实现类时,最终会调用到方法org.slf4j.LoggerFactory#bind,在该方法就会通过SPI机制获取实现类了,如下:

在这里插入图片描述

7:原理

当通过ServiceLoader.load(SayHi.class);执行后,返回是java.util.ServiceLoader,该类实现了java.lang.Iterable接口,因此秘密肯定就藏在iterator方法中,该方法如下:

public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

接着我们执行代码 (SayHi sayHi : load)时会自动调用到next,调用链如下:

在这里插入图片描述
可以看到,会从约定的文件中加载我们配置的实现类全限定名称,并通过反射获取实例,酱。

写在后面

美团:SPI 的原理是什么?

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值