SPI服务发现机制

SPI是Java JDK内部提供的一种服务发现机制。

  • SPI->Service Provider Interface,服务提供接口,是Java JDK内置的一种服务发现机制

  • 通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类

[??注意事项]:
面向对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行编码。如果涉及实现类就会违反可插拔的原则,针对于模块装配,Java SPI提供了为某个接口寻找服务的实现机制。

SPI规范

  • 使用约定:
    [1].编写服务提供接口,可以是抽象接口和函数接口,JDK1.8之后推荐使用函数接口

[2].在jar包的META-INF/services/目录里创建一个以服务接口命名的文件。其实就是实现该服务接口的具体实现类。

提供一个目录:
META-INF/services/
放到ClassPath下面 

[3].当外部程序装配这个模块的时候,就能通过该Jar包META-INF/services/配置文件找到具体的实现类名,并装载实例化,完成模块注入。

目录下放置一个配置文件:
文件名是需要拓展的接口全限定名称
文件内部为要实现的接口实现类
文件必须为UTF-8编码 

[4].寻找服务接口实现,不用在代码中提供,而是利用JDK提供服务查找工具类:java.util.ServiceLoader类来加载使用:

ServiceLoader.load(xxx.class)
ServiceLoader loads = ServiceLoader.load(xxx.class) 

SPI源码分析

[1].ServiceLoader源码:
YMEMlV.png

package java.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.AccessController;
import java.security.AccessControlContext;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

public final class ServiceLoader implements Iterable {
    //[1].初始化定义全局配置文件路径Path
    private static final String PREFIX = "META-INF/services/";
    //[2].初始化定义加载的服务类或接口
    private final Class service;
    //[3].初始化定义类加载器
    private final ClassLoader loader;
    //[4].初始化定义访问控制上下文
    private final AccessControlContext acc;
    //[5].初始化定义加载服务类的缓存集合 
    private LinkedHashMap providers = new LinkedHashMap<>();
    //[6].初始化定义私有内部LazyIterator类,真正加载服务类的实现类
    private LazyIterator lookupIterator;
    
    //私有化有参构造-> ServiceLoader(Class svc, ClassLoader cl)
    private ServiceLoader(Class svc, ClassLoader cl) {   //[1].实例化服务接口->Class service = Objects.requireNonNull(svc, "Service interface cannot be null");
    //[2].实例化类加载器->ClassLoader
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    //[3].实例化访问控制上下文->AccessControlContext
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    //[4].回调函数->reload
    reload();
    }
    
    public void reload() {
    //[1].清空缓存实例集合
    providers.clear();
    //[2].实例化私有内部LazyIterator类->LazyIterator
    lookupIterator = new LazyIterator(service, loader);
    }
    
    public static  ServiceLoader load(Class service,ClassLoader loader)
    {
     return new ServiceLoader<>(service, loader);
    }
    
    public static  ServiceLoader load(Class service) {
      ClassLoader cl = Thread.currentThread().getContextClassLoader();
      return ServiceLoader.load(service, cl);
    } 
    
 } 

2.LazyIterator源码:
YMeW11.png

 private class LazyIterator implements Iterator {

    Class service;
    ClassLoader loader;
    Enumeration configs = null;
    Iterator pending = null;
    String nextName = null;

    private LazyIterator(Class 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;
        }
        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
    }

    public boolean hasNext() {
      if (acc == null) {
        return hasNextService();
      } else {
        PrivilegedAction action =
            new PrivilegedAction() {
              public Boolean run() {
                return hasNextService();
              }
            };
        return AccessController.doPrivileged(action, acc);
      }
    }

    public S next() {
      if (acc == null) {
        return nextService();
      } else {
        PrivilegedAction action =
            new PrivilegedAction() {
              public S run() {
                return nextService();
              }
            };
        return AccessController.doPrivileged(action, acc);
      }
    }

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

使用举例

[1].Dubbo SPI 机制:

META-INF/dubbo.internal/xxx=接口全限定名 

Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。
Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下。
与Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。
[2].Cache SPI 机制:

 META-INF/service/javax.cache.spi.CachingProvider=xxx 

[3]Spring SPI 机制:

META-INF/services/org.apache.commons.logging.LogFactory=xxx 

[4].SpringBoot SPI机制:

META-INF/spring.factories/org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx 

在springboot的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载的。从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回
源码:

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// spring.factories文件的格式为:key=value1,value2,value3
// 从所有的jar包中找到META-INF/spring.factories文件
// 然后从文件中解析出key=factoryClass类名称的所有value值
public static List loadFactoryNames(Class factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    // 取得资源文件的URL
    Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
    List result = new ArrayList();
    // 遍历所有的URL
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        // 根据资源文件URL解析properties文件,得到对应的一组@Configuration类
        Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
        String factoryClassNames = properties.getProperty(factoryClassName);
        // 组装数据,并返回
        result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
    }
    return result;
} 

[5].自定义序列化实现SPI:META-INF/services/xxx=接口全限定名

参考学习Java SPI 和Dubbo SPI机制源码,自己动手实现序列化工具类等 

版权声明:本文为博主原创文章,遵循相关版权协议,如若转载或者分享请附上原文出处链接和链接来源。

标签:javaspi

Java编程技术之浅析SPI服务发现机制的更多相关文章

  1. java mybatis框架配置详解

    一个框架的使用,必然离不开其中的组件支持。我们在下载完mybatis框架后,因为大部分的内部结构还没有启动,就要手动的对其进行配置。在之前有提到,mybatis框架的作用就有数据库方面的,所以本篇文章带来了数据库和sql方面的配置方法,大家一起往下面看看具体操作。1.配置数据库创建mybatis的配......

  2. Java中的权限修饰符(protected)示例详解

    前言大部分来自:https://blog.csdn.net/justloveyou_/article/details/61672133。并在这个博客的基础上,加上了自己的一些理解。权限控制表修饰词本类同一个包的类继承类其他类private√×××无(默认)√√×......

  3. Java多线程实现简易微信发红包的方法实例

    一、首先我们先大致了解一下什么是多线程。(书上的解释)程序是一段静态的代码,它是应用软件的蓝本。进程是程序的一次动态执行过程,对应了从代码加载执行,执行到执行完毕的一个完整的过程。线程不是进程,线程是比进程更小的执行单位,一个进程在其执行过程中,可以产生多个线程形成多条执行线索,每条线索即每个线程也......

  4. Java中的clone方法实例详解

    Java中对象创建clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象。所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象。那么在java语言中,有几种方式可以创建对象呢?1 使用new操作符创建一个对象2 使用clone方法复制......

  5. java封装实例用法讲解

    我们可以选择把类的方法、属性装起来,便于日后的程序书写和使用,这种处理方法就是封装的思想。因为封装类之后,其他的外部类方法就不能在混入其中,对代码的安全性进行了提高。接下来我们就对java中封装的概念、目的进行介绍,然后在实例中为大家演示封装的方法。1.概念封装性是面向对象三大特征之一,是指一种将抽......

  6. Java中获取时间戳

    Java中获取时间戳 三种方式对比**最近项目开发过程中发现了项目中获取时间戳的业务。而获取时间戳有以下三种方式,首先先声明推荐使用System类来获取时间戳,下面一起看一看三种方式。1.System.currentTimeMillis() System类中的currentTimeMillis()方......

  7. 从JAVA内存到垃圾回收,带你深入理解JVM

    摘要:学过Java的程序员对JVM应该并不陌生,如果你没有听过,没关系今天我带你走进JVM的世界。程序员为什么要学习JVM呢,其实不懂JVM也可以照样写出优质的代码,但是不懂JVM有可能别被面试官虐得体无完肤。§ 1.JAVA内存区域与内存溢出异常§ 1.1运行时数据区域......

  8. Java多线程总结(一)

    多线程作为Java中很重要的一个知识点,在此还是有必要总结一下的。一.线程的生命周期及五种基本状态关于Java中线程的生命周期,首先看一下下面这张较为经典的图:上图中基本上囊括了Java中多线程各重要知识点。掌握了上图中的各知识点,Java中的多线程也就基本上掌握了。主要包括:Java线程具有五中基......

  9. Shiro中Subject对象的创建与绑定流程分析

    我们在平常使用Shrio进行身份认证时,经常通过获取Subject 对象中保存的Session、Principal等信息,来获取认证用户的信息,也就是说Shiro会把认证后的用户信息保存在Subject 中供程序使用public static Subject getSubject(){ return......

  10. 解决java中的父类私有成员变量的继承问题

    如果父类中属性为私有(private),那么能否被子类继承呢?答案是不可以。我们看如下简单代码class Father {private String name;public void sayHi() {System.out.println("My name is " + thi......

随机推荐

  1. Java序列化

    1.什么是序列化Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程:序列化:对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。核心作用是对象......

  2. Python 有可能删除 GIL 吗?

    我们知道,在 CPython 中,有一个全局解释器锁,英文叫 global interpreter lock,简称 GIL,是一个互斥锁,用来保护 Python 世界里的对象,防止同一时刻多个线程执行 Python 的字节码,从而确保线程安全,这导致了 Python 的线程无法利用多核 CPU 的优......

  3. JS中循环遍历数组的四种方式总结

    本文比较并总结遍历数组的四种方式:for 循环:for (let index=0; index < someArray.length; index++) { const elem = someArray[index]; // ··· } for-in 循环:for......

  4. 常见大中型网络WLAN基本业务实例

    组网图形大中型WLAN网络简介本文介绍的WLAN网络是指利用频率为2.4GHz或5GHz的射频信号作为传输介质的无线局域网,相对于有线网络的铺设成本高,不便于网络调整和扩展、位置固定,移动性差等缺点,WLAN网络以其低廉的铺设成本、便捷的网络调整和扩展、灵活的可移动性获得了越来越广泛的应用。组网需求......

  5. 详解java中static关键词的作用

    在java中,static是一个修饰符,用于修饰类的成员方法、类的成员变量,另外可以编写static代码块来优化程序性能;被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。static关键词的作用1、静态成员变量的语法特定2、静态函数的语法特......

  6. Apache压力测试工具的安装使用

    1.下载 进入apache官网 http://httpd.apache.org/ 下载apache即可 2.启动ab 以windows环境下,apache安装路径为C:\apache\Apache24\为例打开cmd命令,输入命令到bin目录cd C:\apache\Apache24\bin 3......

  7. Java集合框架分析(List)——LinkedList类详解

    LinkedList类中的方法与实现原理目录一.数据结构二.类标题三.字段四.构造函数五.方法分析5.1 共有方法 public boolean add(Object o)public boolean addAll(Collection c)public boolean contains(Objec......

  8. Java并发包源码学习系列:阻塞队列实现之LinkedBlockingQueue源码解析

    目录LinkedBlockingQueue概述类图结构及重要字段构造器出队和入队操作入队enqueue出队dequeue阻塞式操作E take() 阻塞式获取void put(E e) 阻塞式插入E poll(timeout, unit) 阻塞式超时获取boolean offer(e, timeou......

  9. JavaScript中的事件委托机制跟深浅拷贝

    今天聊下JavaScript中的事件委托跟深浅拷贝一、事件委托首先呢,介绍一下事件绑定//方法一:通过onclick 点击 function clickEvent(){alert("点击事件");}//方法二:通过addEventListener 点击 var btn = doc......

  10. java中“==“和equals()的区别详解

    今天我们探讨一下Java中"=="与equals()的区别==:关系运算符在基本数据类型中比较两个值的内容是否相等在引用类型型中比较的是两个对象的地址是否相等equals()是Object类中的方法1.基本数据类型无法使用equals()方法2.在引用类型中若是没有重写Objec......