SPI机制

基本概述

SPI 全称 Service Provider Interface ,是一种服务发现机制。通过提供接口、预定义的加载器( Loader )以及约定俗称的配置(一般在 META-INF 目录下),可以实现动态加载服务实现类。

类图

通过类图可以分析出, ServiceLoader 实现了 Iterable 接口,提供了迭代的功能。

ServiceLoader 将迭代的实现委托给 LazyIterator

LazyIterator 提供了延时迭代的能力,当有需要的时候,才去加载。

Skywalking 模块中的使用

接口定义

org.apache.skywalking.oap.server.library.module.ModuleDefine

package org.apache.skywalking.oap.server.library.module;import java.lang.reflect.Field;
import java.util.Enumeration;
import java.util.Properties;
import java.util.ServiceLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** A module definition.*/
public abstract class ModuleDefine implements ModuleProviderHolder {private static final Logger LOGGER = LoggerFactory.getLogger(ModuleDefine.class);private ModuleProvider loadedProvider = null;private final String name;public ModuleDefine(String name) {this.name = name;}/*** @return the module name**/public final String name() {return name;}/*** @return the {@link Service} provided by this module.*/public abstract Class[] services();/*** Run the prepare stage for the module, including finding all potential providers, and asking them to prepare.** @param moduleManager of this module* @param configuration of this module* @throws ProviderNotFoundException when even don't find a single one providers.*/void prepare(ModuleManager moduleManager, ApplicationConfiguration.ModuleConfiguration configuration,ServiceLoader<ModuleProvider> moduleProviderLoader) throws ProviderNotFoundException, ServiceNotProvidedException, ModuleConfigException, ModuleStartException {// etc...}// etc...@Overridepublic final ModuleProvider provider() throws DuplicateProviderException, ProviderNotFoundException {if (loadedProvider == null) {throw new ProviderNotFoundException("There is no module provider in " + this.name() + " module!");}return loadedProvider;}
}

接口实现

org.apache.skywalking.oap.server.library.module.BaseModuleA

package org.apache.skywalking.oap.server.library.module;public class BaseModuleA extends ModuleDefine {public BaseModuleA() {super("BaseA");}// 需要提供服务的接口@Overridepublic Class<? extends Service>[] services() {return new Class[] {ServiceABusiness1.class,ServiceABusiness2.class};}public interface ServiceABusiness1 extends Service {void print();}public interface ServiceABusiness2 extends Service {}
}

META-INF 定义

META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine

org.apache.skywalking.oap.server.library.module.BaseModuleA

使用方式

org.apache.skywalking.oap.server.library.module.ModuleManager#init

    /*** Init the given modules*/public void init(ApplicationConfiguration applicationConfiguration) /* etc... */ {// SPI机制加载ServiceLoaderModuleDefine> moduleServiceLoader = ServiceLoader.load(ModuleDefine.class);// 迭代器获取for (ModuleDefine module : moduleServiceLoader) {// do something// etc...}// etc...}

源码解析

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<S> implements Iterable<S> {// 目录前缀private static final String PREFIX = "META-INF/services/";// 需要被加载对象的Class对象private final Class<S> service;// 类加载器private final ClassLoader loader;// The access control context taken when the ServiceLoader is createdprivate final AccessControlContext acc;// 加载对象缓存(按实例化顺序排序)private LinkedHashMap<String,S> providers = new LinkedHashMap<>();// 当前使用的懒加载迭代器private LazyIterator lookupIterator;// 重载public void reload() {// 清除加载对象缓存providers.clear();// 重置懒加载迭代器lookupIterator = new LazyIterator(service, loader);}// 不允许直接创建ServiceLoader对象,只能通过loadXXX获取ServiceLoader对象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();}private static void fail(Class<?> service, String msg, Throwable cause) throws ServiceConfigurationError {throw new ServiceConfigurationError(service.getName() + ": " + msg, cause);}private static void fail(Class<?> service, String msg) throws ServiceConfigurationError {throw new ServiceConfigurationError(service.getName() + ": " + msg);}private static void fail(Class<?> service, URL u, int line, String msg) throws ServiceConfigurationError {fail(service, u + ":" + line + ": " + msg);}// 解析配置文件中的一行,如果没有注释,则加入到类名列表中private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names) throws IOException, ServiceConfigurationError {String ln = r.readLine();if (ln == null) {return -1;}int ci = ln.indexOf('#');if (ci >= 0) ln = ln.substring(0, ci);ln = ln.trim();int n = ln.length();if (n != 0) {if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))fail(service, u, lc, "Illegal configuration-file syntax");int cp = ln.codePointAt(0);if (!Character.isJavaIdentifierStart(cp))fail(service, u, lc, "Illegal provider-class name: " + ln);for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {cp = ln.codePointAt(i);if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))fail(service, u, lc, "Illegal provider-class name: " + ln);}if (!providers.containsKey(ln) && !names.contains(ln))names.add(ln);}return lc + 1;}// 解析配置文件,返回实现类名列表private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError {InputStream in = null;BufferedReader r = null;ArrayList<String> names = new ArrayList<>();try {in = u.openStream();r = new BufferedReader(new InputStreamReader(in, "utf-8"));int lc = 1;while ((lc = parseLine(service, u, r, lc, names)) >= 0);} catch (IOException x) {fail(service, "Error reading configuration file", x);} finally {try {if (r != null) r.close();if (in != null) in.close();} catch (IOException y) {fail(service, "Error closing configuration file", y);}}return names.iterator();}// 懒加载迭代器,提供了延时迭代的能力,当有需要的时候,才去加载private class LazyIterator implements Iterator<S> {// 需要被加载对象的Class对象Class<S> service;// 类加载器ClassLoader loader;// 配置文件列表Enumeration<URL> configs = null;// 当前迭代的配置文件中类名列表的迭代器Iterator<String> pending = null;// 下一个实现类名String nextName = null;private LazyIterator(Class<S> service, ClassLoader loader) {this.service = service;this.loader = loader;}// 是否有下一个Serviceprivate boolean hasNextService() {if (nextName != null) {return true;}// 加载所有配置文件if (configs == null) {try {String fullName = PREFIX + service.getName();if (loader == null)configs = ClassLoader.getSystemResources(fullName);elseconfigs = 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;}// 获取下一个Serviceprivate S nextService() {if (!hasNextService())throw new NoSuchElementException();String cn = nextName;nextName = null;Class<?> c = null;try {// 类名 -> 类的Class对象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<Boolean> action = new PrivilegedAction<Boolean>() {public Boolean run() { return hasNextService(); }};return AccessController.doPrivileged(action, acc);}}// 迭代器,获取下个元素public S next() {if (acc == null) {return nextService();} else {// 授权资源PrivilegedAction<S> action = new PrivilegedAction<S>() {public S run() { return nextService(); }};return AccessController.doPrivileged(action, acc);}}// 不支持删除public void remove() {throw new UnsupportedOperationException();}}// 迭代器实现,如果有缓存从缓存中获取,没有则从懒加载迭代器加载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;return lookupIterator.hasNext();}public S next() {if (knownProviders.hasNext())return knownProviders.next().getValue();return lookupIterator.next();}public void remove() {throw new UnsupportedOperationException();}};}// 通过类的Class对象及类加载,获取ServiceLoaderpublic static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {return new ServiceLoader<>(service, loader);}// 通过类的Class对象,获取ServiceLoaderpublic static <S> ServiceLoader<S> load(Class<S> service) {ClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);}// 通过类的Class对象和扩展类加载器,获取ServiceLoaderpublic static <S> ServiceLoader<S> loadInstalled(Class<S> service) {ClassLoader cl = ClassLoader.getSystemClassLoader();ClassLoader prev = null;while (cl != null) {prev = cl;cl = cl.getParent();}return ServiceLoader.load(service, prev);}public String toString() {return "java.util.ServiceLoader[" + service.getName() + "]";}}

PS: JDK 提供的 SPI 机制,必须要使用迭代器遍历获取需要的实现,而 Dubbo SPI 可以通过 #getExtension 获取指定实现类。

总结

通过源码分析,可以了解到 Skywalking 没有定义自己的 SPI 机制,但深入阅读 Skywalking 的使用场景后,发现用 JDK 提供的 SPI 机制也没什么问题。

个人认为,任何技术都应该根据场景选取,适合的才是最好的,如果没有那么复杂的需要,没必要像 dubbo 一样,定义自己的 SPI 机制。

参考文档

  • Java Service Provider Interface
  • Dubbo SPI

Skywalking-12:Skywalking SPI机制相关推荐

  1. Skywalking-13:Skywalking模块加载机制

    模块加载机制 基本概述 Module 是 Skywalking 在 OAP 提供的一种管理功能特性的机制.通过 Module 机制,可以方便的定义模块,并且可以提供多种实现,在配置文件中任意选择实现. ...

  2. 深入理解java SPI机制

    What? SPI机制(Service Provider Interface)其实源自服务提供者框架(Service Provider Framework,参考[EffectiveJava]page6 ...

  3. JDK源码分析——Java的SPI机制分析与实战

    重点提示:在我博客中的所有的源码分析的实例,我都将会放到github上,感兴趣的朋友可以下载下来调试运行,我相信还是可以有所收获的.我的目的是让所有读到我博客的朋友都可以了解到有价值的东西,学习到ja ...

  4. Dubbo 源码分析 - SPI 机制

    1.简介 SPI 全称为 Service Provider Interface,是一种服务发现机制.SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类.这样可以 ...

  5. spi 动态加载、卸载_理解 ServiceLoader类与SPI机制

    对于Java中的Service类和SPI机制的透彻理解,也算是对Java类加载模型的掌握的不错的一个反映. 了解一个不太熟悉的类,那么从使用案例出发,读懂源代码以及代码内部执行逻辑是一个不错的学习方式 ...

  6. 深入了解Java的SPI机制

    导语   SPI的全名叫做Service Provider Interface,在java.util.ServiceLoader的文档中有详细的介绍,下面就来通过简单的例子实现SPI深入了解   我们 ...

  7. Java中的ClassLoader和SPI机制

    深入探讨 Java 类加载器 成富是著名的Java专家,在IBM技术网站发表很多Java好文,也有著作. 线程上下文类加载器 线程上下文类加载器(context class loader)是从 JDK ...

  8. Java的SPI机制

    Dubbo等框架使用到必须掌握. java.sql.Driver 是 Spi,com.mysql.jdbc.Driver 是 Spi 实现,其它的都是 Api. package org.hadoop. ...

  9. JDK/Dubbo/Spring 三种 SPI 机制,谁更好?

    点击关注公众号,Java干货及时送达 来源:juejin.cn/post/6950266942875779108 SPI 全称为 Service Provider Interface,是一种服务发现机 ...

最新文章

  1. DevXpress 控件: 第一篇: 将 Master_Details 关系进行到底--XtraPivotGridControl控件
  2. Android studio 真机联调闪退
  3. java web手动部署_tomcat手动部署web项目的方法
  4. [转]asp.net权限认证:HTTP基本认证(http basic)
  5. 中缀转后缀表达式并求值
  6. 关于提高代码复用性的几个知识点的回顾
  7. OpenCv:椭圆上点的计算方程
  8. catboost进行分类并开启GPU模式
  9. dnf机械机器人补丁_干货 | 详解工业机器人控制系统架构
  10. FIle类和递归方法的使用
  11. 异常信息_共同药业被爆:大客户异常 瞒报环保处罚信息
  12. CCNA题库大换血,考生纷纷落马!(转)
  13. 2021 Alexa SmartHome Skill 对接(二)配置及服务对接
  14. matlab中的hold on/off、grid on/off 命令使用方法
  15. windows文件权限管理dos命令
  16. 5秒内克隆你的声音,并生成任何内容,这个工具细思极恐...还特么的开源~
  17. 计算机蓝屏了 怎么维修,电脑蓝屏怎么解决
  18. WebUploader上传图片插件
  19. QCC3040---Message Broker module
  20. nginx中deny和allow详解

热门文章

  1. mysql 数据库链路_MySQL数据库使用(二):配置、连接远端数据库
  2. 【提交PR】如何在 GitHub 提交第一个 pull request
  3. 【静态站点(一)】之 静态网站生成器
  4. 几行代码实现谷歌百度搜索对比
  5. [BJDCTF2020]Cookie is so stable
  6. C#LeetCode刷题-深度优先搜索
  7. 以太坊同步节点_如何同步以太坊节点而不会犯我的错误
  8. Anaconda简介:它是什么,以及如何安装
  9. 119_Power Pivot 长尾明细显示为【其他】
  10. php函数巧用 array_column