Skywalking-12:Skywalking SPI机制
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...
@Override
public 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");
}
// 需要提供服务的接口
@Override
public 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 created
private 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;
}
// 是否有下一个Service
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;
}
// 获取下一个Service
private 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对象及类加载,获取ServiceLoader
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
return new ServiceLoader<>(service, loader);
}
// 通过类的Class对象,获取ServiceLoader
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
// 通过类的Class对象和扩展类加载器,获取ServiceLoader
public 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 机制。
参考文档
分享并记录所学所见
Skywalking-12:Skywalking SPI机制的更多相关文章
- Skywalking-13:Skywalking模块加载机制
模块加载机制 基本概述 Module 是 Skywalking 在 OAP 提供的一种管理功能特性的机制.通过 Module 机制,可以方便的定义模块,并且可以提供多种实现,在配置文件中任意选择实现. ...
- Java SPI机制简介
SPI 简介 SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制. 目前有不少框架用它来做服务的扩展发现, 简单来说,它就是一种动态替换发现 ...
- JDK源码系列(一) ------ 深入理解SPI机制
什么是SPI机制 最近我建了另一个文章分类,用于扩展JDK中一些重要但不常用的功能. SPI,全名Service Provider Interface,是一种服务发现机制.它可以看成是一种针对接口实现 ...
- Java的SPI机制
目录 1. 什么是SPI 2. 为什么要使用SPI 3. 关于策略模式和SPI的几点区别 4. 使用介绍或者说约定 4.1 首先介绍几个名词 4.2 约定 5. 具体的demo实现 5.1 创建服务提 ...
- 利用SPI机制实现责任链模式中的处理类热插拔
最近看到责任链模式的时候每增加一个处理类,就必须在责任链的实现类中手动增加到责任链中,具体代码基本就是list.add(new FilterImpl()),可以看到每次增加一个处理类,就必须添加一行上 ...
- 一文搞懂Java/Spring/Dubbo框架中的SPI机制
几天前和一位前辈聊起了Spring技术,大佬突然说了SPI,作为一个熟练使用Spring的民工,心中一紧,咱也不敢说不懂,而是在聊完之后赶紧打开了浏览器,开始的学习之路,所以也就有了这篇文章.废话不多 ...
- Java SPI机制,你了解过吗?
Life moves pretty fast,if you don't stop and look around once in a while,you will miss it 为什么需要SPI? ...
- java中的SPI机制
1 SPI机制简介 SPI的全名为Service Provider Interface.大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的.在java.util.ServiceLoader的文档里 ...
- java 的SPI机制
今天看到spring mvc 使用Java Validation Api(JSR-303)进行校验,需要加载一个 其具体实现(比如Hibernate Validator), 本来没有什么问题,但是突然 ...
随机推荐
- C# wpf中关于binding的converter无效的情况
最近碰到bingding设置了convert转换无效的问题.困扰了我好久.这里记录分析一下. 先说下现象 我把TextBox的text属性 绑定到了对应的 convert.代码如下 希望吧pd_no ...
- css - 响应式
css - 响应式 移动设备尺寸 移动设备的尺寸各不相同,大体上可以做如下划分: 768px以下的是手机屏幕 768px-991px是平板ipad屏幕 992px-1199是大平板屏幕 1200极其以 ...
- Java程序设计学习笔记(五) — 多线程
时间:2016-4-15 09:56 --多线程(还有多核编程) 1.进程 进程是一个正在执行中的程序. 每一个进程执行都有一个执行顺序,该顺序是一个执行路径, ...
- excel快捷键如下:
ALT+ 空格键,然后按下 X ALT+ 空格键,然后按下 R 首先打开表格,在A1对角用鼠标左键单击,界面会全部选中,然后调整字体大小框里的数字,回车,表格就变大了. 同时按Alt和E,再按L ...
- 使用 antd 的 form 组件来自定义提交的数据格式
最近使用antd UI 的表单提交数据,数据里面有的是数组,有的是对象.提交的时候还要去校验参数,让人非常头疼.在我仔细看完文档之后,发现 antd 的 form 组件做的非常不错,这些需求通通不是问 ...
- AbpVnext使用分布式IDistributedCache Redis缓存(自定义扩展方法)
AbpVnext使用分布式IDistributedCache缓存from Redis(带自定义扩展方法) 我的依赖包的主要版本以及Redis依赖如下 1:添加依赖 <PackageReferen ...
- Python - 面向对象编程 - 新式类和旧式类
object object 是 Python 为所有对象提供的父类,默认提供一些内置的属性.方法:可以使用 dir 方法查看 新式类 以 object 为父类的类,推荐使用 在 Python 3.x ...
- Apache Hudi内核之文件标记机制深入解析
1. 摘要 Hudi 支持在写入时自动清理未成功提交的数据.Apache Hudi 在写入时引入标记机制来有效跟踪写入存储的数据文件. 在本博客中,我们将深入探讨现有直接标记文件机制的设计,并解释了其 ...
- 理解ASP.NET Core - [04] Host
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 本文会涉及部分 Host 相关的源码,并会附上 github 源码地址,不过为了降低篇幅,我会 ...
- xshell与小键盘问题
有些程序员的键盘是带有小数字键的,在使用xshell中文版时就可能出现一些小状况,本集就同大家分析一下使用数字键盘出现乱码的情况怎么办. 图1:使用数字小键盘出现乱码 问题描述: 在xshell上用v ...