Java SPI机制详解

SPI介绍

SPI ,全称为 Service Provider Interface,是一种服务发现机制,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。<<高可用可伸缩微服务架构>> 第3章 Apache Dubbo 框架的原理与实现 中有这样的一句定义.SPI是 JDK 内置的一种服务提供发现功能, 一种动态替换发现的机制. 举个例子, 要想在运行时动态地给一个接口添加实现, 只需要添加一个实现即可.

Java SPI的实现规范

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hrquStXG-1614250376060)(_media/…/…/…/…/_media/image/structure/javaspi.jpg)]
    也就是说在我们代码中的实现里, 无需去写入一个 Factory 工厂, 用 MAP 去包装一些子类, 最终返回的类型是父接口. 只需要定义好资源文件, 让父接口与它的子类在文件中写明, 即可通过设置好的方式拿到所有定义的子类对象:

ServiceLoader<Interface> loaders = ServiceLoader.load(Interface.class)
for(Interface interface : loaders){
System.out.println(interface.toString());
}

这种方式相比与普通的工厂模式, 肯定是更符合开闭原则, 新加入一个子类不用去修改工厂方法, 而是编辑资源文件.

  • 按照Java SPI规范实现SPI

编写SPI接口和实现类

public interface Fruit {

    /**
     * name
     * @return name
     */
    String name();
}

public class Apple implements Fruit {

    @Override
    public String name() {
        return "Apple";
    }
}

public class Orange implements Fruit {

    @Override
    public String name() {
        return "Orange";
    }
}

resource目录下创META-INF/services/目录,并在该目录下创建以SPI接口全路径名的文件com.test.spi.Fruit,文件内容如下:

com.test.spi.Apple
com.test.spi.Orange

编写SPI测试类

public class FruitTest {

    @Test
    public void testFruit() {
        ServiceLoader<Fruit> serviceLoader = ServiceLoader.load(Fruit.class);
        for (Fruit fruit : serviceLoader) {
            System.out.println(fruit.name());
        }
    }
}

项目结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H5ArW4ZY-1614250376062)(_media/…/…/…/…/_media/image/java/spi/project-1.jpg)]

Java SPI源码分析

  • ServiceLoader类SPI配置文件路径
public final class ServiceLoader<S>
    implements Iterable<S> {
    private static final String PREFIX = "META-INF/services/";
}
  • ServiceLoader类SPI核心实现
    public static <S> ServiceLoader<S> load(Class<S> service) {
        // 获取当前线程的ClassLoader
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

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

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        // 初始化ServiceLoader属性
        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();
        // 创建懒加载的迭代器
        lookupIterator = new LazyIterator(service, loader);
    }
  • LazyIterator类反射创建对象过程分析

LazyIterator实现了Iterator,所以是在遍历迭代器时候通过反射创建的对象

    private class LazyIterator
        implements Iterator<S> {
        // 接口全路径
        Class<S> service;
        // 类加载器
        ClassLoader loader;
        // 配置文件对象
        Enumeration<URL> configs = null;
        // 迭代器
        Iterator<String> pending = null;
        // SPI实现类全路径名
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        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;
                }
                // 获取SPI类的全路径名
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }
        // 反射创建对象
        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
        }
    }

总结

Java的SPI实现的主流程如下:

  • 使用IO流读取配置资源文件
  • 实现迭代器接口和懒加载的模式在遍历阶段创建对象
  • 使用反射创建对象并放到缓存中

下节我们参考dubbo来看下dubbo的SPI是怎么实现的

来源:https://www.icode9.com/content-1-870201.html

(0)

相关推荐

  • (17条消息) Java类加载器

    ContextClassLoader是一种与线程相关的类加载器,类似ThreadLocal,每个线程对应一个上下文类加载器.在实际使用时一般都用下面的经典结构: ClassLoader targetC ...

  • Java SPI 与 Dubbo SPI

    SPI(Service Provider Interface)是JDK内置的一种服务提供发现机制.本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类.这样可以在运行时,动 ...

  • 高级开发必须理解的Java中SPI机制

    本文通过探析JDK提供的,在开源项目中比较常用的Java SPI机制,希望给大家在实际开发实践.学习开源项目提供参考. 1 SPI是什么 SPI全称Service Provider Interface ...

  • 麻黄黄芪为什么能治疗肾炎、白癜风?麻黄黄芪配对的作用机制详解

    麻黄黄芪为什么能治疗肾炎、白癜风?麻黄黄芪配对的作用机制详解

  • SPI接口详解

    SPI(Serial Peripheral Interface,串行外围设备接口)是由Motorola公司开发,用来在微控制器和外围设备芯片之间提供一个低成本.易使用的接口(SPI有时候也被称为4线接 ...

  • DNS 之 EDNS机制详解

    EDNS 机制详解 随着业务的复杂化和多样化,RFC1035中定义的DNS消息格式和它支持的消息内容已经不足以满足一些DNS服务器的需求,于是,RFC2671中提出了一种扩展DNS机制EDNS(Ext ...

  • Swarm和IPFS工作机制有什么区别? BZZ挖矿机制详解

    Swarm项目主网上线也很近了,它的进度超乎我们的预期,但是我们很多朋友呢,对这个Swarm的工作机制以及它的代币BZZ的挖坑机制并不了解,所以我们今天利给大家简单梳理一下,Swarm的这个工作机制, ...

  • Swarm的奖励机制详解

    在Swarm的背景下,存储和带宽是两个最重要的有限资源,这在其激励计划中得到了体现.使用带宽的动机旨在实现快速.可靠的数据提供.而存储动机旨在确保长期保存数据.这样,可以确保满足Web应用程序开发的所 ...

  • 【治疗2型糖尿病——葛根芩连汤机制详解】...

    [治疗2型糖尿病--葛根芩连汤机制详解] 葛根芩连汤出自<伤寒论>,由葛根.黄芩.黄连.甘草组成. 具有解表清里之功效,主治协热下利. [降糖成分]有效成分为葛根素.黄芩苷等黄酮类和小檗碱 ...

  • 麻黄干姜为什么能治疗遗尿、风湿?麻黄干姜配对的作用机制详解

    麻黄干姜为什么能治疗遗尿、风湿?麻黄干姜配对的作用机制详解

  • 视频号算法推荐机制详解! | 青瓜传媒

    视频号已经越来越多的人进入其中研究探讨了,探讨最多的就是视频号数据算法的问题,第一批吃螃蟹的人多少尝到了一些甜头.如今,明星.网红.媒体.公众号和部分已经在其他平台有内容影响力的个人,成了视频号第一批 ...

  • 今日头条的运营规则和推荐机制详解|实战干货

    今天我们聊点关于头条号的运营干货,聊这个我最兴奋了,因为我是头条号名利双收的受益者之一.既然要说头条号,那咱们就先从他的推荐算法说起: 今日头条的推荐机制和抖音平台的推荐机制基本类似,当你上传一篇文章 ...