剖析 SPI 在 Spring 中的应用

🚀 优质资源分享 🚀

学习路线指引(点击解锁) 知识定位 人群定位
🧡 Python实战微信订餐小程序 🧡 进阶级 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
💛Python量化交易实战💛 入门级 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

vivo 互联网服务器团队 - Ma Jian

一、概述

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

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

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

二、Java SPI

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

2.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文件

图片

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的实现类(懒加载)
        ServiceLoader dataBaseSpis = ServiceLoader.load(DataBaseSPI.class);
 // ServiceLoader实现了Iterable,故此处可以使用for循环遍历加载到的实现类
 for(DataBaseSPI spi : dataBaseSpis){
 spi.dataBaseOperation();
 }
 }
}


⑤ 运行结果:

Operate Mysql database!!!
Operate Oracle database!!!


2.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<>();
 
 // 内部类,实现了Iterator接口
 private LazyIterator lookupIterator;
}~~~~~~

从源码中可以发现:

  • ServiceLoader类本身实现了Iterable接口并实现了其中的iterator方法,iterator方法的实现中调用了LazyIterator这个内部类中的方法,解析完服务提供接口文件后最终结果放在了Iterator中返回,并不支持服务提供接口实现类的直接访问。
  • 所有服务提供接口的对应文件都是放置在META-INF/services/目录下,final类型决定了PREFIX目录不可变更。

所以Java内置的SPI机制思想是非常好的,但其内置实现上的不足也很明显。

三、Dubbo SPI

Dubbo SPI沿用了Java SPI的设计思想,但在实现上有了很大的改进,不仅可以直接访问扩展类,而且在访问的灵活性和扩展的便捷性都做了很大的提升。

3.1 基本概念

① 扩展点

一个Java接口,等同于服务提供接口,需用@SPI注解修饰。

② 扩展

扩展点的实现类。

③ 扩展类加载器:ExtensionLoader

类似于Java SPI的ServiceLoader,主要用来加载并实例化扩展类。一个扩展点对应一个扩展加载器。

④ Dubbo扩展文件加载路径

Dubbo框架支持从以下三个路径来加载扩展类:

  • META-INF/dubbo/internal
  • META-INF/dubbo
  • META-INF/services

Dubbo框架针对三个不同路径下的扩展配置文件对应三个策略类:

  • DubboInternalLoadingStrategy
  • DubboLoadingStrategy
  • ServicesLoadingStrategy

三个路径下的扩展配置文件并没有特殊之处,一般情况下:

  • META-INF/dubbo对开发者开放
  • META-INF/dubbo/internal 用来加载Dubbo内部的扩展点
  • META-INF/services 兼容Java SPI

⑤ 扩展配置文件

和Java SPI不同,Dubbo的扩展配置文件中扩展类都有一个名称,便于在应用中引用它们。

如:Dubbo SPI扩展配置文件

#扩展实例名称=扩展点实现类
adaptive=org.apache.dubbo.common.compiler.support.AdaptiveCompiler
jdk=org.apache.dubbo.common.compiler.support.JdkCompiler
javassist=org.apache.dubbo.common.compiler.support.JavassistCompiler


3.2 Dubbo SPI

先通过代码来演示下 Dubbo SPI 的实现。

① 创建扩展点(即服务提供接口)

扩展点

package dubbo.spi;
 
import org.apache.dubbo.common.extension.SPI;
 
@SPI  // 注解标记当前接口为扩展点
public interface DataBaseSPI {
    public void dataBaseOperation();
}


② 创建扩展点实现类

  • MysqlDataBaseSPIImpl

扩展类1

package dubbo.spi.impl;
 
import dubbo.spi.DataBaseSPI;
 
public class MysqlDataBaseSPIImpl implements DataBaseSPI {
 
    @Override
    public void dataBaseOperation() {
        System.out.println("Dubbo SPI Operate Mysql database!!!");
    }
}


  • OracleDataBaseSPIImpl

扩展类2

package dubbo.spi.impl;
 
import dubbo.spi.DataBaseSPI;
 
public class OracleDataBaseSPIImpl implements DataBaseSPI {
 
    @Override
    public void dataBaseOperation() {
        System.out.println("Dubbo SPI Operate Oracle database!!!");
    }
}


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

图片

dubbo.spi.DataBaseSPI

#扩展实例名称=扩展点实现类
mysql = dubbo.spi.impl.MysqlDataBaseSPIImpl
oracle = dubbo.spi.impl.OracleDataBaseSPIImpl


PS:文件内容中,等号左边为该扩展类对应的扩展实例名称,右边为扩展类(内容格式为一行一个扩展类,多个扩展类分为多行)

④ 运行代码:

DubboSpiTest#main()

package dubbo.spi;
 
import org.apache.dubbo.common.extension.ExtensionLoader;
 
public class DubboSpiTest {
 
    public static void main(String args[]){
        // 使用扩展类加载器加载指定扩展的实现
        ExtensionLoader dataBaseSpis = ExtensionLoader.getExtensionLoader(DataBaseSPI.class);
 // 根据指定
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值