关于后端:深入剖析-Spring-Boot-的-SPI-机制

42次阅读

共计 2884 个字符,预计需要花费 8 分钟才能阅读完成。

大家好,我是不才陈某~

SPI(Service Provider Interface) 是 JDK 内置的一种服务提供发现机制,能够用来启用框架扩大和替换组件, 次要用于框架中开发,例如 Dubbo、Spring、Common-Logging,JDBC 等采纳采纳 SPI 机制,针对同一接口采纳不同的实现提供给不同的用户,从而进步了框架的扩展性。

之前也写过 Java SPI 的深刻分析:聊聊 Java SPI 机制

举荐 Java 工程师技术指南:https://github.com/chenjiabin…

Java SPI 实现

Java 内置的 SPI 通过 java.util.ServiceLoader 类解析 classPath 和 jar 包的 META-INF/services/ 目录 下的以接口全限定名命名的文件,并加载该文件中指定的接口实现类,以此实现调用。

示例阐明

创立动静接口

public interface VedioSPI
{void call();
}

实现类 1

public class Mp3Vedio implements VedioSPI
{
    @Override
    public void call()
    {System.out.println("this is mp3 call");
    }

}

实现类 2

public class Mp4Vedio implements VedioSPI
{
    @Override
    public void call()
    {System.out.println("this is mp4 call");
    }

}

在我的项目的 source 目录下新建 META-INF/services/ 目录下,创立 com.skywares.fw.juc.spi.VedioSPI 文件。

相干测试

public class VedioSPITest
{public static void main(String[] args)
    {ServiceLoader<VedioSPI> serviceLoader =ServiceLoader.load(VedioSPI.class);
        
        serviceLoader.forEach(t->{t.call();
        });
    }
}

阐明:Java 实现 spi 是通过 ServiceLoader 来查找服务提供的工具类。

运行后果:

源码剖析

上述只是通过简略的示例来实现下 java 的内置的 SPI 性能。其实现原理是 ServiceLoader 是 Java 内置的用于查找服务提供接口的工具类,通过调用 load() 办法实现对服务提供接口的查找,最初遍从来一一拜访服务提供接口的实现类。

从源码能够发现:

  • ServiceLoader 类自身实现了 Iterable 接口并实现了其中的 iterator 办法,iterator 办法的实现中调用了 LazyIterator 这个外部类中的办法,迭代器创立实例。
  • 所有服务提供接口的对应文件都是搁置在 META-INF/services/ 目录下,final 类型决定了 PREFIX 目录不可变更。

尽管 java 提供的 SPI 机制的思维十分好,然而也存在相应的弊病。具体如下:

  • Java 内置的办法形式只能通过遍从来获取
  • 服务提供接口必须放到 META-INF/services/ 目录下。

针对 java 的 spi 存在的问题,Spring 的 SPI 机制沿用的 SPI 的思维,但对其进行扩大和优化。

举荐 Java 工程师技术指南:https://github.com/chenjiabin…

Spring SPI

Spring SPI 沿用了 Java SPI 的设计思维,Spring 采纳的是 spring.factories 形式实现 SPI 机制,能够在不批改 Spring 源码的前提下,提供 Spring 框架的扩展性。

Spring 示例

定义接口

public interface DataBaseSPI
{void getConnection();
}

相干实现

##DB2 实现
public class DB2DataBase implements DataBaseSPI
{
    @Override
    public void getConnection()
    {System.out.println("this database is db2");
    }

}

##Mysql 实现
public class MysqlDataBase implements DataBaseSPI
{
    @Override
    public void getConnection()
    {System.out.println("this is mysql database");
    }

}

1、在我的项目的 META-INF 目录下,新增 spring.factories 文件

2、填写相干的接口信息,内容如下:

com.skywares.fw.juc.springspi.DataBaseSPI = com.skywares.fw.juc.springspi.DB2DataBase, com.skywares.fw.juc.springspi.MysqlDataBase

阐明多个实现采纳逗号分隔。

相干测试类

public class SpringSPITest
{public static void main(String[] args)
    {
         List<DataBaseSPI> dataBaseSPIs =SpringFactoriesLoader.loadFactories(DataBaseSPI.class, 
                 Thread.currentThread().getContextClassLoader());
         
         for(DataBaseSPI datBaseSPI:dataBaseSPIs){datBaseSPI.getConnection();
         }
    }
}

输入后果

从示例中咱们看出,Spring 采纳 spring.factories 实现 SPI 与 java 实现 SPI 十分类似,然而 spring 的 spi 形式针对 java 的 spi 进行的相干优化具体内容如下:

  • Java SPI 是一个服务提供接口对应一个配置文件,配置文件中寄存以后接口的所有实现类,多个服务提供接口对应多个配置文件,所有配置都在 services 目录下;
  • Spring factories SPI 是一个 spring.factories 配置文件寄存多个接口及对应的实现类,以接口全限定名作为 key,实现类作为 value 来配置,多个实现类用逗号隔开,仅 spring.factories 一个配置文件。

那么 spring 是如何通过加载 spring.factories 来实现 SpI 的呢? 咱们能够通过源码来进一步剖析。

举荐 Java 工程师技术指南:https://github.com/chenjiabin…

源码剖析

阐明:loadFactoryNames 解析 spring.factories 文件中指定接口的实现类的全限定名,具体实现如下:

阐明:获取所有 jar 包中 META-INF/spring.factories 文件门路,以枚举值返回。遍历 spring.factories 文件门路,一一加载解析,整合 factoryClass 类型的实现类名称,获取到实现类的全类名称后进行类的实例话操作,其相干源码如下:

阐明:实例化是通过反射来实现对应的初始化。

正文完
 0