揭秘JDK SPI

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等,还是有必要了解一下的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,810评论 19 139
  • SPI框架实现之旅二:整体设计 上一篇简单的说了一下spi相关的东西, 接下来我们准备开动,本篇博文主要集中在一些...
    一灰灰blog阅读 5,366评论 0 5
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 47,118评论 6 342
  • 乌云一朵朵掩盖了蔚蓝的天空,微凉的风吹起发丝。没一会儿,雨便噼里啪啦的砸下来,传来一阵泥土自然的清香。 夏果连忙撑...
    汐夏夏阅读 1,454评论 5 1
  • 老实,听话,真干!六字真言中,老实放在第一位。老实就是要遵守规则,心无杂念!但是现在的学校一不小心就培养出了...
    伏羲师范熊芳阅读 3,587评论 0 3

友情链接更多精彩内容