揭秘JDK SPI

作者: 数齐 | 来源:发表于2017-02-07 11:21 被阅读212次

SPI的全称是Service Provider Interface,它是为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 JAVA SPI就是提供这样的一个机制,为某个接口寻找服务实现的机制。
我们举一个小例子加以说明
首先我有一个接口IHello.java

public interface IHello {
    void sayHello();
}

有两个接口的实现类HelloImpl1,HelloImpl2

public class HelloImpl1 implements IHello {
    @Override
    public void sayHello() {
        System.out.println("我是Impl1");
    }
}

public class HelloImpl2 implements IHello {
    @Override
    public void sayHello() {
        System.out.println("我是Impl2");
    }
}

在MATE-INF下面的services文件夹中有一个以接口的全限定名命名的文件,里面的内容就是实现类的全限定名。
使用时使用帮助类ServiceLoader,下面是我们具体的使用方法

    public static void main(String[] args){
        ServiceLoader<IHello> s = ServiceLoader.load(IHello.class);
        Iterator<IHello> iHelloIterator = s.iterator();
        while (iHelloIterator.hasNext()) {
            IHello iHello = iHelloIterator.next();
            iHello.sayHello();
        }
    }

我们大体解释一下,搜索IHello类型的实例,之后遍历执行,我们并没有指明是具体的哪个IHello的实现类进行操作,那么是谁指定的呢?就是META-INF/services 下面的那个文件内容决定的,如果内容是:

com.test.demo.impl.HelloImpl2

那么就由Impl2执行,如果是

com.test.demo.impl.HelloImpl1
com.test.demo.impl.HelloImpl2

就是两个都执行。在我们不需要改动代码的情况下可以动态的切换执行的实现类。
ServiceLoader是怎样执行的呢?
首先创建这个类型的ServiceLoader

    public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

当需要的属性都准备完毕,开始reload

    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();
    }

关注这个LazyIterator的操作,查询实现类都是通过这个类进行的。

    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

这个方法是LazyIterator判断有没有下一个服务的方法,我们可以先看一下
这个fullName就是前缀(META-INF/services/)加上要搜索服务的name组成,所以我们的文件的路径及名称都已经确定了,只能这么写,否则搜索不到,我们再来看一下parse方法,就是解析文件中的内容放到Iterator中

private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

下面是获取service的过程,经过上一步,基本上都解析出来实现类的名称了,所以要通过Class.forName反射加载这个类的Class对象,然后实例化(newInstance),最后返回。

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

上面就是这个ServiceLoader执行的流程。这个SPI大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的,现在很多软件都已采用或者扩展了这种。比如说dubbo,jdbc等,还是有必要了解一下的。

相关文章

  • 揭秘JDK SPI

    SPI的全称是Service Provider Interface,它是为了实现在模块装配的时候能不在程序里动态指...

  • 【dubbo源码】6.dubbo的spi机制

    Dubbo的SPI由JDK标准的SPI加强而来 Dubbo 改进了 JDK 标准的 SPI 的以下问题: JDK ...

  • 1.0 dubbo源码解析之@SPI

    @SPI是了解dubbo源码的基础,dubbo-spi是通过jdk-spi优化而来的,对于jdk-spi的原理这里...

  • 阅读dubbo源码之jdk spi机制

    一、JDK SPI 1、jdk spi是什么? 全称:Service Provider Interface. 2、...

  • Dubbo SPI 机制

    前提 Dubbo的SPI是从JDK的SPI 扩展加强而来的。 JDK中SPI 机制如下(可以查看JDBC的实现) ...

  • Dubbo插件化SPI

    一、SPI SPI 简介 SPI 全称为 (Service Provider Interface) ,是JDK内置...

  • jdk-spi的实现原理

    2.1 jdk-spi的实现原理 dubbo-spi是在jdk-spi的基础上进行重写优化,下面看一下jdk-sp...

  • DUBBO SPI实现核心-ExtensionLoader

    概念 dubbo spi是dubbo对JDK spi的升级,针对JDK spi的一些弱势进行了优化,官网的介绍如下...

  • Java SPI

    [TOC] 1. Java SPI SPI (Service Provider Interface) 是 JDK ...

  • SPI机制(jdk, dubbo, spring)

    1.SPI概述 2.jdk中的SPI机制(jdk1.6开始) 2.1基本要求 3.dubbo中的SPI机制 htt...

网友评论

    本文标题:揭秘JDK SPI

    本文链接:https://www.haomeiwen.com/subject/bxvpittx.html