Java和Dubbo的SPI

SPI: 简单理解就是,你一个接口有多种实现,然后在代码运行时候,具体选用那个实现,这时候我们就可以通过一些特定的方式来告诉程序寻用那个实现类,这就是SPI。

JAVA的SPI

全称为 Service Provider Interface,是一种服务发现机制。它是约定在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,然后文件里面记录的是此 jar 包提供的具体实现类的全限定名。

这样当我们引用了某个 jar 包的时候就可以去找这个 jar 包的 META-INF/services/ 目录,再根据接口名找到文件,然后读取文件里面的内容去进行实现类的加载与实例化。

例如:

java的jdbc就使用了SPI机制,当我项目种应用了mysql的连接jar时候,就会去去mysql-connector-java.jar下的META-INF/services/ 目录查找java.sql.Driver名的文件,然后加载里面全类名的类。如果使用oracle连接驱动时候,就会去ojdbc.jar下面去找java.sql.Driver文件里的配置的全类名。
在这里插入图片描述

在这里插入图片描述
并且通过IDEA的智能提示功能,也能看到,在你切换不同连接的jar包时候,Driver接口实现类是不同的。

使用mysql的连接驱动:

在这里插入图片描述

切换到oracle的连接驱动:

在这里插入图片描述

Java的SPI机制源码分析

下面这段代码,以jdbc的SPI为例,可以作为debug的入口:

package com.example.demo;

import java.sql.Connection;
import java.sql.DriverManager;
/**
 * @author:luzaichun
 * @Date:2021/3/14
 * @Time:14:09
 **/
public class JDBCMain {
    private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=true&useUnicode=true&characterEncoding=UTF-8";

    public static void main(String[] args) throws Exception{
        Connection conn = DriverManager.getConnection(URL, "root", "123456");
    }
}

在使用DriverManager.getConnection()方法时候,会加载并初始化DriverManager类,此类是jdbc使用SPI的核心类。

  1. DriverManager类初始化,调用static代码块,执行DriverManager#loadInitialDrivers()方法
    在这里插入图片描述
  2. 使用javaSPI的核心类ServiceLoader#load()和以及其内部实现了Iterator的LazyIterator#hasNext()和
    LazyIterator#next(),加载接口的具体实现类。
    在这里插入图片描述
    ServiceLoader.load()整个代码流程,如下图。其实就是给LazyIterator类的赋值属性,是那个接口要进行SPI,使用的类加载器是哪一个。
    在这里插入图片描述
driversIterator.hasNext()和driversIterator.next()方法负责类实际类的加载
  • driversIterator.hasNext()最后实际是调到了LazyIterator.hasNext();
  • driversIterator.next()最后实际是调到了LazyIterator.next();

hashNext()方法读到SPI的配置文件里的全类名
在这里插入图片描述
next()方法最后通过反射创建出具体实现类的实例
在这里插入图片描述
总结:

  1. jdbc的SPI,通过DriverManager类静态代码块执行loadInitialDrivers()方法
  2. 然后通过ServiceLoader.load()拿到具体的接口,以及类加载器。
  3. 通过实现了Iterator类的LazyIterator类的hasNext方法读取配置文件,拿到接口的具体实现全类名
  4. 在next()方法内部,通过反射机制,由实现类的全类名,加载具体实现类。
代码实战java SPI

DemoService接口

public interface DemoService {
    String sayHello(String msg);
} 

XiaoHongDemoServiceImpl实现类

public class XiaoHongDemoServiceImpl implements DemoService {
    @Override
    public String sayHello(String msg) {
        return "xiaohong:"+msg;
    }
}

ZhangSanDemoServiceImpl实现类

public class ZhangSanDemoServiceImpl implements DemoService {
    @Override
    public String sayHello(String msg) {
        return "zhangsan:"+msg;
    }
}

定义SPI配置文件
在这里插入图片描述

最后使用

public class DemoMain {
    public static void main(String[] args) {
        ServiceLoader<DemoService> serviceLoad = ServiceLoader.load(DemoService.class);
        Iterator<DemoService> iterator = serviceLoad.iterator();
        while (iterator.hasNext()){
            DemoService demoService = iterator.next();
            String returnStr = demoService.sayHello("lzc贼帅!!!!");
            System.out.println(returnStr);
        }
    }
}

执行结果:
在这里插入图片描述

java SPI劣势,会加载SPI配置文件里定义的所有配置类,如果用不上该类,也会加载。通俗点讲,就是无法按需加载。


Dubbo的SPI

dubbo SPI使用

需要先引入dubbo相关的依赖

  1. 定义接口
    通过dubbo的SPI注解标注定义的接口
@SPI("xiaohong")
public interface DubboSPIService {

    void sayHello();
}
  1. 多个实现类
public class XiaoHongDubboSPIServiceImpl implements DubboSPIService {
    @Override
    public void sayHello() {
        System.out.println("小红说:lzc贼帅!");
    }
}
public class XiaoMingDubboSPIServiceImpl implements DubboSPIService {
    @Override
    public void sayHello() {
        System.out.println("小明说:lzc贼帅!");
    }
}
  1. 定义dubbo SPI配置文件
    META-INF/dubbo目录下定义接口全类名的文件,配置key-value的实现

Dubbo 对配置文件目录的约定,不同于 Java SPI ,Dubbo 分为了三类目录。

  • META-INF/services/ 目录:该目录下的 SPI 配置文件是为了用来兼容 Java SPI 。
  • META-INF/dubbo/ 目录:该目录存放用户自定义的 SPI 配置文件。
  • META-INF/dubbo/internal/ 目录:该目录存放 Dubbo 内部使用的 SPI 配置文件。

在这里插入图片描述

  1. 使用
public class DubboSPIMain {
    public static void main(String[] args) {
    //default,会取@SPI注解里定义的key对应的实现
//        DubboSPIService defaultExtensionService = ExtensionLoader.getExtensionLoader(DubboSPIService.class).getDefaultExtension();
//        defaultExtensionService.sayHello();
        DubboSPIService dubboSPIService = ExtensionLoader.getExtensionLoader(DubboSPIService.class).getExtension("xiaoming");
        dubboSPIService.sayHello();
    }
}

结果:
在这里插入图片描述

源码分析

ExtensionLoader.getExtensionLoader(DubboSPIService.class).getExtension("xiaoming");
dubbo SPI的核心就是ExtensionLoader类

1.ExtensionLoader#getExtensionLoader()

该方法主要是,从一个map里取key为当前传进来的接口Class的value(value是ExtensionLoader对象),如果取不到,我们就往这个map里put一份这样的key-value。value是new ExtensionLoader(type)传进去的type是接口的Class对象,最后会赋值给ExtensionLoader对象的type属性,后面会用到
在这里插入图片描述

2.拿到ExtensionLoader对象后,通过ExtensionLoader#getExtension()获取具体的实现的实例

首先会取缓存里拿,没拿到就调用createExtension()方法取创建所需要的实例,最后塞入缓存。
在这里插入图片描述

3.createExtension方法

通过getExtension(“xiaoming”)传进来的name=xiaoming,从SPI配置文件获取到所需要实现类的全类名,通过反射拿到实现类的Class对象,最后通过反射拿到相应的实例。核心是getExtensionClasses()方法。
在这里插入图片描述

4.getExtensionClasses()

getExtensionClasses()方法返回一个Map,key为SPI配置文件中的key,value为SPI配置文件中,实现类的Class对象。

  • 可以看到,代码中用来大量的缓存机制,锁的双检查。
  • cacheDefaultExtensionName()方法里会拿到SPI注解上配置的默认key,然后赋值给cachedDefaultName属性,如果使用getDefaultExtension()时候会使用到
  • strategies,其实是通过java得SPI拿到得一个数组
    在这里插入图片描述
5.循环三个SPI文件得目录,分别调用loadDirectory方法

fileName最后在三次循环里,会拼出三个路径,META-INF/dubbo/com.example.demo.service.DubboSPIService,这一个才是正确得路径,然后获得配置文件得绝对路径。然后会执行loadResource()方法读取SPI配置文件

  • META-INF/dubbo/com.example.demo.service.DubboSPIService
  • META-INF/services/com.example.demo.service.DubboSPIService
  • META-INF/dubbo/internal/com.example.demo.service.DubboSPIService
    在这里插入图片描述
    在这里插入图片描述
6.loadResource()读取SPI配置文件

一行一行读配置文件里得key-value,然后通过Class.forName()获取类得Class对象。然后put到第四步定义得空Map,extensionClasses这个Map里,再返回到第三步得getExtensionClasses()方法。

在这里插入图片描述
好了,今天先到这里,凌晨了。。。Adaptive 注解 - 自适应扩展下次有时间再写。

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值