剖析 SPI 在 Spring 中的应用

一、概述

SPI(Service Provider Interface),是 Java 内置的一种服务提供发现机制,可以用来提高框架的扩展性,主要用于框架的开发中,比如 Dubbo,不同框架中实现略有差异,但核心机制相同,而 Java 的 SPI 机制可以为接口寻找服务实现。SPI 机制将服务的具体实现转移到了程序外,为框架的扩展和解耦提供了极大的便利。

得益于 SPI 优秀的能力,为模块功能的动态扩展提供了很好的支撑。

本文会先简单介绍 Java 内置的 SPI 和 Dubbo 中的 SPI 应用,重点介绍分析 Spring 中的 SPI 机制,对比 Spring SPI 和 Java 内置的 SPI 以及与 Dubbo SPI 的异同。

剖析 SPI 在 Spring 中的应用插图亿华云

二、Java SPI

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

1. Java SPI

先通过代码来了解下 Java SPI 的实现

① 创建服务提供接口:

package jdk.spi;

// 接口

public interface DataBaseSPI {

public void dataBaseOperation();

}

② 创建服务提供接口的实现类

MysqlDataBaseSPIImpl

实现类 1:

package jdk.spi.impl;

import jdk.spi.DataBaseSPI;

public class MysqlDataBaseSPIImpl implements DataBaseSPI {

@Override

public void dataBaseOperation() {

System.out.println("Operate Mysql database!!!");

}

}OracleDataBaseSPIImpl

实现类 2:

package jdk.spi.impl;

import jdk.spi.DataBaseSPI;

public class OracleDataBaseSPIImpl implements DataBaseSPI {

@Override

public void dataBaseOperation() {

System.out.println("Operate Oracle database!!!");

}

}

③ 在项目 META-INF/services/ 目录下创建 jdk.spi.DataBaseSPI 文件

剖析 SPI 在 Spring 中的应用插图1亿华云

jdk.spi.DataBaseSPI:

jdk.spi.impl.MysqlDataBaseSPIImpl

jdk.spi.impl.OracleDataBaseSPIImpl

④ 运行代码:

JdkSpiTest#main()

package jdk.spi;

import java.util.ServiceLoader;

public class JdkSpiTest {

public static void main(String args[]){

// 加载jdk.spi.DataBaseSPI文件中DataBaseSPI的实现类(懒加载)

ServiceLoaderdataBaseSpis = ServiceLoader.load(DataBaseSPI.class);

// ServiceLoader实现了Iterable,故此处可以使用for循环遍历加载到的实现类

for(DataBaseSPI spi : dataBaseSpis){

spi.dataBaseOperation();

}

}

}

⑤ 运行结果:

Operate Mysql database!!!

Operate Oracle database!!!2. 源码分析

上述实现即为使用 Java 内置 SPI 实现的简单示例,ServiceLoader 是 Java 内置的用于查找服务提供接口的工具类,通过调用 load () 方法实现对服务提供接口的查找 (严格意义上此步并未真正的开始查找,只做初始化),最后遍历来逐个访问服务提供接口的实现类。

上述访问服务实现类的方式很不方便,如:无法直接使用某个服务,需要通过遍历来访问服务提供接口的各个实现,到此很多同学会有疑问:

Java 内置的访问方式只能通过遍历实现吗?服务提供接口必须放到 META-INF/services/ 目录下?是否可以放到其他目录下?

在分析源码之前先给出答案:两个都是的;Java 内置的 SPI 机制只能通过遍历的方式访问服务提供接口的实现类,而且服务提供接口的配置文件也只能放在 META-INF/services/ 目录下。

ServiceLoader 部分源码:

public final class ServiceLoader implements Iterable{

// 服务提供接口对应文件放置目录

private static final String PREFIX = "META-INF/services/";

// The class or interface representing the service being loaded

private final Class service;

// 类加载器

private final ClassLoader loader;

// The access control context taken when the ServiceLoader is created

private final AccessControlContext acc;

// 按照初始化顺序缓存服务提供接口实例

private LinkedHashMap providers = new LinkedHashMap

THE END
Copyright © 2024 亿华云