JAVA SPI

约定如下:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/ 目录中同时创建一个以服务接口命名的文件,该文件中的内容就是实现该服务接口的具体实现类。

Java中提供了一个用于服务实现查找的工具类:java.util.ServiceLoader。

//将服务声明的文件名称定义为: example.spi.service.IService,与接口名称一致,其中的内容包括:
//example.spi.service.PrintServiceImpl
//example.spi.service.EchoServiceImpl  public static void main(String[] args) {  //实例化具体类时需要注意对应类有无参构造函数ServiceLoader<Service> serviceLoader = ServiceLoader.load(IService.class);  for (IService service : serviceLoader) {  service.printInfo();  }
}  

ServiceLoader的源码分析

重要属性:

// 加载的接口
private Class<S> service;// 用于缓存已经加载的接口实现类,其中key为实现类的完整类名
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();// 用于延迟加载接口的实现类
private LazyIterator lookupIterator;

第一步:获取一个ServiceLoader<Service> serviceLoader = ServiceLoader.load(IService.class);实例,此时还没有进行任何接口实现类的加载操作,属于延迟加载类型的。只是创建了LazyIterator lookupIterator对象而已。

第二步:ServiceLoader实现了Iterable接口,即实现了该接口的iterator()方法,实现内容如下:

    // for循环遍历ServiceLoader的过程其实就是调用上述hasNext()和next()方法的过程public Iterator<S> iterator() {return new Iterator<S>() {Iterator<Map.Entry<String,S>> knownProviders= providers.entrySet().iterator();public boolean hasNext() {if (knownProviders.hasNext())return true;// 第一次循环遍历会使用lookupIterator去查找,之后就缓存到providers中。          // LazyIterator会去加载类路径下/META-INF/services/接口全称 文件的url地址,文件加载并解析完成之后,得到一系列的接口实现类的完整类名。          // 调用next()方法时才回去真正执行接口实现类的加载操作,并根据无参构造器创建出一个实例,存到providers中。return lookupIterator.hasNext();}public S next() {//再次遍历ServiceLoader,就直接遍历providers中的数据if (knownProviders.hasNext())return knownProviders.next().getValue();return lookupIterator.next();}public void remove() {throw new UnsupportedOperationException();}};}    

ServiceLoader缺点

  1. 虽然ServiceLoader使用延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。
  2. 获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类

Dubbo SPI

dubbo的扩展机制与JAVA SPI比较类似,但额外增加了其他功能:

  1. 可以根据接口名称来获取服务,dubbo spi 可以通过getExtension(String key)的方法方便的获取某一个想要的扩展实现,java的SPI机制需要加载全部的实现类。
  2. 服务声明文件支持A=B的方式,此时A为名称B为实现类。 文件名:com.alibaba.dubbo.rpc.Filter
    • echo=com.alibaba.dubbo.rpc.filter.EchoFilter

    • generic=com.alibaba.dubbo.rpc.filter.GenericFilter

    • genericimpl=com.alibaba.dubbo.rpc.filter.GenericImplFilter

  3. 支持扩展IOC依赖注入功能,可以为Service之间的依赖关系注入相关的服务并保证单例。
    • 举例来说:接口A,实现者A1、A2。接口B,实现者B1、B2。

      现在实现者A1含有setB()方法,会自动注入一个接口B的实现者,此时注入B1还是B2呢?

      都不是,而是注入一个动态生成的接口B的实现者B$Adpative,该实现能够根据参数的不同,自动引用B1或者B2来完成相应的功能。

      Protocol$Adpative是根据URL参数中protocol属性的值来选择具体的实现类的。

      如值为dubbo,则从ExtensionLoader<Protocol>中获取dubbo对应的实例,即DubboProtocol实例

      如值为hessian,则从ExtensionLoader<Protocol>中获取hessian对应的实例,即HessianProtocol实例

      也就是说Protocol$Adpative能够根据url中的protocol属性值动态的采用对应的实现。

  4. 对扩展采用装饰器模式进行功能增强,类似AOP实现的功能

    • 接口A的另一个实现者AWrapper1。在获取某一个接口A的实现者A1的时候,已经自动被AWrapper1包装了。

      private A a;
      AWrapper1(A a){this.a=a;
      }

ExtensionLoader源码分析

ExtensionLoader<Protocol> protocolLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol protocol = protocolLoader.getAdaptiveExtension();@Extension("dubbo")
public interface Protocol {int getDefaultPort();@Adaptive<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;@Adaptive<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;void destroy();
}

第一步:根据要加载的接口创建出一个ExtensionLoader实例

重要属性: ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

用于缓存所有的扩展加载实例,这里加载Protocol.class,就以Protocol.class为key,创建的ExtensionLoader为value存储到上述EXTENSION_LOADERS中。

第二步:ExtensionLoader实例是加载Protocol的实现类

  1. 先解析Protocol上的Extension注解的name,存至String cachedDefaultName属性中,作为默认的实现
  2. 到类路径下的加载 META-INF/services/com.alibaba.dubbo.rpc.Protocol文件,然后就是读取每一行内容,加载对应的class。(扩展配置文件: /META-INF/dubbo/internal,/META-INF/dubbo/,META-INF/services)
  3. 上述class分成三种情况来处理,对于一个接口的实现者,ExtensionLoader分三种情况来分别存储对应的实现者,属性分别如下:Class<?> cachedAdaptiveClass;Set<Class<?>> cachedWrapperClasses;Reference<Map<String, Class<?>>> cachedClasses;
    • 情况1: 如果这个class含有Adaptive注解,则将这个class设置为Class<?> cachedAdaptiveClass。
    • 情况2: 尝试获取有对应接口参数的构造器,如果能够获取到,则说明这个class是一个装饰类即需要存到Set<Class<?>> cachedWrapperClasses中
    • 情况3: 如果没有上述构造器。则获取class上的Extension注解,根据该注解的定义的name作为key,存至Reference<Map<String, Class<?>>> cachedClasses结构中

转载于:https://www.cnblogs.com/wade-luffy/p/8578812.html

SPI(Service Provider Interface)机制相关推荐

  1. Java中的SPI(Service Provider Interface介绍及示例

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 一个服务 ...

  2. Java基础学习总结(145)——Java SPI(Service Provider Interface)简介

    SPI 简介 SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制.一个服务(Service)通常指的是已知的接口或者抽象类,服务提供方就是对 ...

  3. SPI(Service Provider Interface)详解

    介绍 SPI 全称为 (Service Provider Interface) ,是JVM内置的一种服务提供发现机制.Java在语言层面为我们提供了一种方便地创建可扩展应用的途径.我们只需要按照SPI ...

  4. SPI : Service Provider Interface

    1.概述 jdk6中引入了一个新特性,使得我们可以根据一个指定的「接口」去找到并加载指定的「实现」. 本篇文章会详细介绍下SPI的用法及场景. 2.关键组成 以java.sql.Driver为例 Se ...

  5. java provider_Java SPI(Service Provider Interface)

    //ServiceLoader实现了Iterable接口,可以遍历所有的服务实现者 public final class ServiceLoader implements Iterable{//查找配 ...

  6. SPI(Service Provider Interface)

    ServiceLoad中的spi 1.简介 JDK1.6引入的特性,用来实现SPI(Service Provider Interface),一种服务发现机制. 2.JDBC举例 2.1.引入mysql ...

  7. Service Provider Interface(SPI)

    目录 1.什么是SPI 2.SPI的使用 3.源码分析: 4.应用 1.什么是SPI SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口 ...

  8. Dubbo源码分析(三)Dubbo中的SPI和自适应扩展机制

    前言 我们在往期文章中,曾经深入分析过Java的SPI机制,它是一种服务发现机制.具体详见:深入理解JDK的SPI机制 在继续深入Dubbo之前,我们必须先要明白Dubbo中的SPI机制.因为有位大神 ...

  9. LSP(Layered Service Provider)入门的基础知识概念

    1.套接字 TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点,这种端点就叫做套接字(socket)或插口. 套接字用(IP地址:端口号)表示. 它是网络通信过程中端点的抽象表示,包含进行网 ...

  10. Writing a Winsock 2 Layered Service Provider

    Wei Hua, Jim Ohlund, Barry Butterklee 著 来源:http://greatdong.blog.edu.cn 作者:董岩 译 greatdong_2001@163.c ...

最新文章

  1. Linux 下的hiredis的简单安装、测试*(转)
  2. mysql 获取数据列号_如何获得mysql数据库的所有的列
  3. 使鼠标保持按住状态_MouseInc 鼠标手势工具
  4. 让C/C++程序员告诉你什么叫浪漫,表白黑科技,炫酷多彩求爱利器
  5. win7/8/10安装过程中将动态磁盘转为basic
  6. 显示文字_如何观看HBO Max并显示中文字幕?
  7. 在IIS上SSL的部署和启动SSL安全
  8. 易宝支付(Java实现)
  9. win10系统下载 Ghost Win10 RS1 1607 32位纯净3月版
  10. 手工修复Win 7下彩影ARP防火墙驱动程序(适用于32/64位系统)
  11. 无法初始化windows sockets
  12. 怎么登录微信公众号-微信公众号使用教程2
  13. 儋州“炰米”:美味的特制粮食
  14. python语句中print(type(1j))_Python语句 print(type(1/2))的输出结果是:______
  15. 纪伯伦 《先知》 孩子
  16. Kotlin真的值得学习吗?
  17. 在CMD里使用命令实现CMD全屏
  18. 电脑工作人员如何保护眼睛
  19. AFMG全球战略发布会,一场与豪华纯电智能跑车的零距离之约
  20. 仿微信群聊头像(图像合成、缩放)

热门文章

  1. 计算机存储地址如何,计算机内存地址只有5种表现形式吗,为什么?
  2. dedecms channel php,DedeCMS在{dede:channel}标签前加序列号
  3. maven项目中操作mysql数据库案例
  4. 计算机操作系统笔记(三)
  5. 腾讯云 mysql 连接_远程连接腾讯云的mysql
  6. batchsize和模型精度的影响
  7. python 判断文件夹是否存在 否则创建
  8. keras.preprocessing.image函数比较
  9. 强烈推荐一个在线caffe网络可视化工具!!
  10. python数字图像处理(10):图像简单滤波