关键知识点提炼:

Java spi 最大优点:热插拔

  • 实现类方法 通过pom依赖 服务接口感知。服务启动时通过依赖查找对应META-INF目录下是否有文件指定方法全路径,有的话初始化。  通过切断依赖控制实现方法的初始化.
  • 跨应用调用机制:A应用引B应用库B-lib,里面都是HSF接口的接口类,具体实现在B应用代码里。

Java SPI 和 Springboot SPI

java插拔式架构设计 java 可插拔 模块 设计_ide

java插拔式架构设计 java 可插拔 模块 设计_后端_02

一、

面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候不用在程序里动态指明,这就需要一种服务发现机制。java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。这有点类似IOC的思想,将装配的控制权移到了程序之外。

什么情况应该把接口放入调用方,什么时候可以把接口归为实现方?

java插拔式架构设计 java 可插拔 模块 设计_java_03

1、实现方提供了接口和实现,我们可以引用接口来达到调用某实现类的功能,这就是我们经常说的api,它具有以下特征:

  1. 概念上更接近实现方
  2. 组织上位于实现方所在的包中
  3. 实现和接口在一个包中

2、当接口属于调用方时,我们就将其称为spi,全称为:service provider interface,spi的规则如下:

  1. 概念上更依赖调用方
  2. 组织上位于调用方所在的包中
  3. 实现位于独立的包中(也可认为在提供方中)

java插拔式架构设计 java 可插拔 模块 设计_java_04

定义:

在jdk6里面引进的一个新的特性ServiceLoader,从官方的文档来说,它主要是用来装载一系列的service provider。而且ServiceLoader可以通过service provider的配置文件来装载指定的service provider。当服务的提供者,提供了服务接口的一种实现之后,我们只需要在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入

spi的思想,接口的实现由provider实现,provider只用在提交的jar包里的META-INF/services下根据平台定义的接口新建文件,并添加进相应的实现类内容就好。

Spring

Spring中运用到spi思想的地方也有很多

scan

我们在spring中可以通过component-scan标签来对指定包路径进行扫描,只要扫到spring制定的@service、@controller等注解,spring自动会把它注入容器。

这就相当于spring制定了注解规范,我们按照这个注解规范开发相应的实现类或controller,spring并不需要感知我们是怎么实现的,他只需要根据注解规范和scan标签注入相应的bean,这正是spi理念的体现。

举例:

当服务的提供者提供了一种接口的实现之后,需要在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,此文件记录了该 jar 包提供的服务接口的具体实现类。当某个应用引入了该 jar 包且需要使用该服务时,JDK SPI 机制就可以通过查找这个 jar 包的 META-INF/services/ 中的配置文件来获得具体的实现类名,进行实现类的加载和实例化,最终使用该实现类完成业务功能。

java插拔式架构设计 java 可插拔 模块 设计_后端_05

首先我们需要创建一个 Log 接口,来模拟日志打印的功能:

public interface Log {
    void log(String info);
}

接下来提供两个实现—— Logback 和 Log4j,分别代表两个不同日志框架的实现,如下所示:

public class Logback implements Log {

    @Override
    public void log(String info) {
        System.out.println("Logback:" + info);
    }

}

public class Log4j implements Log {

    @Override
    public void log(String info) {
        System.out.println("Log4j:" + info);
    }

}

在项目的 resources/META-INF/services 目录下添加一个名为 com.zzz.Log 的文件,这是 JDK SPI 需要读取的配置文件,具体内容如下:

com.zzz.impl.Log4j
com.zzz.impl.Logback

最后创建 main() 方法,其中会加载上述配置文件,创建全部 Log 接口实现的实例,并执行其 log() 方法,如下所示:  遍历接口的实现

public class Main {

    public static void main(String[] args) {
        ServiceLoader<Log> serviceLoader =  ServiceLoader.load(Log.class);
        Iterator<Log> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            Log log = iterator.next();
            log.log("JDK SPI");
        }
    }

}

// 输出如下:
// Log4j:JDK SPI
// Logback:JDK SPI