Java学习:SPI机制

1 是什么

Java Service Provider Interface(Java spi,Java服务提供程序接口),是在Java 6引入的用于发现和加载指定接口匹配实现类的功能,简称服务程序提供接口。
主要好处:面向接口编程,将服务定义和具体实现分离,实现解耦,实现动态加载模块化。
主要定义了四个组件,具体信息如下,这四者关系如下图:
在这里插入图片描述

1.1 服务

一组编程接口和实现类,提供应用的功能,或对外部资源的访问能力。

1.2 服务提供者接口

充当服务的接口SPI

1.3 服务提供商

SPI的具体实现,通过放到资源目录META-INF /services中的提供商配置文件进行配置和识别的。文件名是 SPI 的完全限定名,其内容是 SPI 实现的完全限定名。

1.4 服务加载器

加载我们特定目录的实现类,SPI 的核心是 ServiceLoader 类。这具有延迟发现和加载实现的作用。它使用上下文类路径来查找提供程序实现,并将它们放在内部缓存中。

2 源码学习

主要是利用ServiceLoader加载实现类,并放在内部缓存中,服务加载器就是ServiceLoader。
以Driver加载数据库驱动为例,我们从如下测试入手:

//load作用是实例化ServiceLoader,设置内部相应属性
ServiceLoader<Driver> load = ServiceLoader.load(Driver.class);
		//获取ServiceLoader的惰性迭代器
        Iterator<Driver> driverIterator = load.iterator();
        //hasNext执行真正的加载步骤
        while (driverIterator.hasNext()) {
            System.out.println(driverIterator.next().getClass().getName());
        }

2.1 ServiceLoader.load(Driver.class)处理逻辑

进入load方法

public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

获取当前线程的上下文 ClassLoader,并调用load方法,该方法就是执行`return new ServiceLoader<>(service, loader),构造函数如下:

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

主要是判空处理,和获取安全管理器,并执行reload方法:

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

获取内部类LazyIterator实例,通过上述步骤实例化了ServiceLoader和如下属性:
serviceloader属性

2.2 加载实现类

获取ServiceLoader内部的迭代器:

public Iterator<S> iterator() {
		//返回迭代器实例
        return new Iterator<S>() {
			//缓存已经知晓的实现类和名字
            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();
			//实现Iterator里hashNext和next方法主要是利用内部类LazyIterator的hashNext和next方法,返回迭代器不支持移除
            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

由于在前边实例化ServiceLoader时执行了providers.clear(),所有返回的迭代器实例knownProviders为null,惰性加载就是当我们执行前边测试程序driverIterator.hasNext()时执行加载实例化过程。
进入到LazyIterator的hasNext:
在这里插入图片描述
可以看出主要是调用内部的hashNextService方法

private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                	//获取资源位置并加载资源:这里为META-INF/services/java.sql.Driver
                    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;
                }
                //解析资源,按照行进行解析,去除空格..放入name列表,并返回name的迭代器
                pending = parse(service, configs.nextElement());
            }
            //实现类全限名赋予nextName
            nextName = pending.next();
            return true;
        }

主要流程:

  1. 获取资源位置并加载资源:这里为META-INF/services/java.sql.Driver
  2. 解析资源,按照行进行解析,去除空格…放入name列表,并返回name的迭代器
  3. 实现类全限名赋予nextName

进入到LazyIterator的next:
在这里插入图片描述
主要是进入到nextService()

private S nextService() {
			//判断是否存在服务
            if (!hasNextService())
                throw new NoSuchElementException();
            //获取要加载的全限定名
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
            	//将类加载进jvm
                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缓存
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

主要流程:

  1. 判断是否存在服务实现类
  2. 获取要加载的全限定名
  3. 将类加载进jvm
  4. 断定是否实现自服务接口
  5. 创建实例,放入providers缓存,返回实例

3 扩展实例

首先定义接口模块、实现类模块、接口和实现类的start模块(方便导入)和测试模块

3.1 接口模块

定义子模块hello-son2其中定义接口MyInterface:
在这里插入图片描述

3.2 实现类模块

定义子模块hello-son1,实现类MyImp:
在这里插入图片描述
上图中我们需要创建services目录,并创建对应接口的文件,内容如下:
在这里插入图片描述
文件名定义为全限定接口,内容为实现类全限定名。

3.3 打包和安装到本地仓库

打包时注意将resources内容打包到META-INF下:
在这里插入图片描述
之后创建一个hello-parent-start打包上述两个maven模块:
在这里插入图片描述
由于hello-son1依赖了hello-son2所有这里只添加了hello-son1

3.4 测试模块

首先导入依赖:
在这里插入图片描述
编写测试类:
在这里插入图片描述
运行结果:
在这里插入图片描述

参考文献

[1] Java 服务提供程序接口.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LamaxiyaFc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值