SPI都不知道?还敢说懂Dubbo?面试官怼的我哑口无言啊!!!

简介: 全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制。我们先通过一个很简单的例子来看下它是怎么用的。


image.png

 为什么要讲SPI呢?因为在Dubbo就用到了SPI机制,所以掌握了这部分对于后面的学习还是很有帮助的。

  SPI ,全称为 Service Provider  Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制。我们先通过一个很简单的例子来看下它是怎么用的。

案例介绍

  先定义接口项目

image.png

  然后创建一个扩展的实现,先导入上面接口项目的依赖

    <dependencies>
        <dependency>
            <groupId>com.bobo</groupId>
            <artifactId>JavaSPIBase</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

  然后创建接口的实现

/**
 * SPI:MySQL对于 baseURL 的一种实现
 */
public class MySQLData implements BaseData {
    @Override
    public void baseURL() {
        System.out.println("mysql 的扩展实现....");
    }
}

  然后在resources目录下创建 META-INF/services 目录,然后在目录中创建一个文件,名称必须是定义的接口的全类路径名称。然后在文件中写上接口的实现类的全类路径名称。

image.png

  同样的再创建一个案例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hLdPTrbV-1626441864846)(img\image-20210716162539114.png)]

  然后在测试的项目中测试

    public static void main(String[] args) {
        ServiceLoader<BaseData> providers = ServiceLoader.load(BaseData.class);
        Iterator<BaseData> iterator = providers.iterator();
        while(iterator.hasNext()){
            BaseData next = iterator.next();
            next.baseURL();
        }
    }

  根据不同的导入,执行的逻辑会有不同

image.png

image.png

源码查看

ServiceLoader

  首先来看下ServiceLoader的类结构

   // 配置文件的路径
  private static final String PREFIX = "META-INF/services/";
    // 加载的服务  类或者接口
    private final Class<S> service;
    // 类加载器
    private final ClassLoader loader;
    // 访问权限的上下文对象
    private final AccessControlContext acc;
    // 保存已经加载的服务类
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    // 内部类,真正加载服务类
    private LazyIterator lookupIterator;

load

  load方法创建了一些属性,重要的是实例化了内部类,LazyIterator。

public final class ServiceLoader<S> implements Iterable<S>
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        //要加载的接口
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        //类加载器
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        //访问控制器
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
       reload();
    }
    public void reload() {
        //先清空
        providers.clear();
        //实例化内部类 
        LazyIterator lookupIterator = new LazyIterator(service, loader);
    }
}

  查找实现类和创建实现类的过程,都在LazyIterator完成。当我们调用iterator.hasNext和iterator.next方法的时候,实际上调用的都是LazyIterator的相应方法。

private class LazyIterator implements Iterator<S>{
    Class<S> service;
    ClassLoader loader;
    Enumeration<URL> configs = null;
    Iterator<String> pending = null;
    String nextName = null; 
    private boolean hasNextService() {
        //第二次调用的时候,已经解析完成了,直接返回
        if (nextName != null) {
            return true;
        }
        if (configs == null) {
            //META-INF/services/ 加上接口的全限定类名,就是文件服务类的文件
            //META-INF/services/com.viewscenes.netsupervisor.spi.SPIService
            String fullName = PREFIX + service.getName();
            //将文件路径转成URL对象
            configs = loader.getResources(fullName);
        }
        while ((pending == null) || !pending.hasNext()) {
            //解析URL文件对象,读取内容,最后返回
            pending = parse(service, configs.nextElement());
        }
        //拿到第一个实现类的类名
        nextName = pending.next();
        return true;
    }
}

  创建实例对象,当然,调用next方法的时候,实际调用到的是,lookupIterator.nextService。它通过反射的方式,创建实现类的实例并返回。

private class LazyIterator implements Iterator<S>{
    private S nextService() {
        //全限定类名
        String cn = nextName;
        nextName = null;
        //创建类的Class对象
        Class<?> c = Class.forName(cn, false, loader);
        //通过newInstance实例化
        S p = service.cast(c.newInstance());
        //放入集合,返回实例
        providers.put(cn, p);
        return p; 
    }
}

  看到这儿,我想已经很清楚了。获取到类的实例,我们自然就可以对它为所欲为了!

为帮助开发者们提升面试技能、有机会入职BATJ等大厂公司,特别制作了这个专辑——这一次整体放出。


相关文章
|
3月前
|
存储 负载均衡 Dubbo
深入理解Dubbo-4.Dubbo扩展SPI
深入理解Dubbo-4.Dubbo扩展SPI
76 1
|
4月前
|
缓存 Dubbo Java
趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了
趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了
|
7月前
|
监控 Dubbo Java
由浅入深Dubbo核心源码剖析SPI机制 2
由浅入深Dubbo核心源码剖析SPI机制
30 0
|
7月前
|
缓存 Dubbo Java
由浅入深Dubbo核心源码剖析SPI机制 1
由浅入深Dubbo核心源码剖析SPI机制
53 0
|
3月前
|
缓存 Dubbo Java
Dubbo 第三节_ Dubbo的可扩展机制SPI源码解析
Dubbo会对DubboProtocol对象进⾏依赖注⼊(也就是⾃动给属性赋值,属性的类型为⼀个接⼝,记为A接⼝),这个时候,对于Dubbo来说它并不知道该给这个属性赋什么值,换句话说,Dubbo并不知道在进⾏依赖注⼊时该找⼀个什么的的扩展点对象给这个属性,这时就会预先赋值⼀个A接⼝的⾃适应扩展点实例,也就是A接⼝的⼀个代理对象。在调⽤getExtension去获取⼀个扩展点实例后,会对实例进⾏缓存,下次再获取同样名字的扩展点实例时就会从缓存中拿了。Protocol是⼀个接。但是,不是只要在⽅法上加了。
|
2月前
|
XML 缓存 Dubbo
Dubbo的魔法之门:深入解析SPI扩展机制【八】
Dubbo的魔法之门:深入解析SPI扩展机制【八】
30 0
|
5月前
|
Dubbo Java 应用服务中间件
阿里一面:说一说Java、Spring、Dubbo三者SPI机制的原理和区别
大家好,我是三友~~ 今天来跟大家聊一聊Java、Spring、Dubbo三者SPI机制的原理和区别。 其实我之前写过一篇类似的文章,但是这篇文章主要是剖析dubbo的SPI机制的源码,中间只是简单地介绍了一下Java、Spring的SPI机制,并没有进行深入,所以本篇就来深入聊一聊这三者的原理和区别。
|
5月前
|
缓存 Dubbo Java
Dubbo2.7的Dubbo SPI实现原理细节
Dubbo2.7的Dubbo SPI实现原理细节
32 0
|
6月前
|
Dubbo Java 应用服务中间件
JDK SPI、Spring SPI、Dubbo SPI三种机制的细节与演化
Java SPI(Service Provider Interface)是JDK提供的一种服务发现机制,用于在运行时动态加载和扩展应用程序中的服务提供者。
168 0
|
6月前
|
存储 Dubbo Java
Dubbo第三讲:Dubbo的可扩展机制SPI源码解析
Dubbo第三讲:Dubbo的可扩展机制SPI源码解析

热门文章

最新文章