Motan系列-Motan的SPI插件扩展机制

开源推荐

推荐一款一站式性能监控工具(开源项目)java

Pepper-Metrics是跟一位同事一块儿开发的开源组件,主要功能是经过比较轻量的方式与经常使用开源组件(jedis/mybatis/motan/dubbo/servlet)集成,收集并计算metrics,并支持输出到日志及转换成多种时序数据库兼容数据格式,配套的grafana dashboard友好的进行展现。项目当中原理文档齐全,且所有基于SPI设计的可扩展式架构,方便的开发新插件。另有一个基于docker-compose的独立demo项目能够快速启动一套demo示例查看效果github.com/zrbcool/pep…。若是你们以为有用的话,麻烦给个star,也欢迎你们参与开发,谢谢:)git


进入正题...

0 Motan中的SPI

Motan的SPI与Dubbo的SPI相似,它在Java原生SPI思想的基础上作了优化,而且与Java原生SPI的使用方式很类似。github

介绍Java原生SPI的相关文章有不少,这里再也不赘述。下面主要介绍一下Motan中的SPI机制,先从使用提及。docker

0.1 SPI的使用

下面以实现一个Filter的扩展为例,说说他咋用。数据库

Filter的做用是在 Provider 端接收请求或 Consumer 端发送请求时进行拦截,作一些特别的事情。下面从 Consumer 端的视角,作一个计算单次请求响应时间的统计需求。api

首先须要写一个类,实现 com.weibo.api.motan.filter.Filter 接口的 filter 方法。微信

@SpiMeta(name = "profiler")
public class ProfilerFilter implements Filter {
    
    @Override
    public Response filter(Caller<?> caller, Request request) {
        // 记录开始时间
        long begin = System.nanoTime();
        try {
            final Response response = caller.call(request);
            return response;
        } finally {
            // 打印本次响应时间
            System.out.println("Time cost : " + (System.nanoTime() - begin));
        }
    }
}
复制代码

其次,在 META-INF/services 目录下建立名为 com.weibo.api.motan.filter.Filter 的文件,内容以下:数据结构

# 例如:com.pepper.metrics.integration.motan.MotanProfilerFilter
#全限定名称#.MotanProfilerFilter
复制代码

而后给Protocol配置 filtermybatis

ProtocolConfigBean config = new ProtocolConfigBean();
config.setName("motan");
config.setMaxContentLength(1048576);
config.setFilter("profiler"); // 配置filter
return config;
复制代码

最后在 RefererConfig 中使用这个 ProtocolConfig 便可。架构

BasicRefererConfigBean config = new BasicRefererConfigBean();
config.setProtocol("demoMotan");
// ... 省略其余配置 ...

复制代码

如此一来,在 Consumer 端就能够拦截每次请求,并打印响应时间了。

接下来,继续研究一下Motan是如何作到这件事的。

1 SPI的管理

Motan的SPI的实如今 motan-core/com/weibo/api/motan/core/extension 中。组织结构以下:

motan-core/com.weibo.api.motan.core.extension
    |-Activation:SPI的扩展功能,例如过滤、排序
    |-ActivationComparator:排序比较器
    |-ExtensionLoader:核心,主要负责SPI的扫描和加载
    |-Scope:模式枚举,单例、多例
    |-Spi:注解,做用在接口上,代表这个接口的实现能够经过SPI的形式加载
    |-SpiMeta:注解,做用在具体的SPI接口的实现类上,标注该扩展的名称

复制代码

1.1 内部管理的数据结构

private static ConcurrentMap<Class<?>, ExtensionLoader<?>> extensionLoaders = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
private ConcurrentMap<String, T> singletonInstances = null;
private ConcurrentMap<String, Class<T>> extensionClasses = null;
private Class<T> type;
private ClassLoader classLoader; // 类加载使用的ClassLoader
复制代码

extensionLoaders 是类变量,他管理的是由 @Spi 注解标注的接口与其 ExtensionLoader 的映射,做为全部SPI的全局管理器。

singletonInstances 维护了当前 ExtensionLoader 中的单例扩展。

extensionClasses 维护了当前 ExtensionLoader 全部扩展实例的Class对象,用于建立多例(经过class.newInstance建立)。

type 维护了当前 @Spi 注解标注的接口的 class 对象。

1.2 ExtensionLoader的初始化

Motan 中,能够经过如下方式初始化ExtensionLoader(以上文中的Filter SPI为例):

// 初始化 Filter 到全局管理器 `extensionLoaders` 中
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class);

复制代码

而后咱们具体看一下 ExtensionLoader.getExtensionLoader(Filter.class) 都干了啥。

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    // 检查type是否为空、type是不是接口类型、type是否被@Spi标注,检查失败会抛出异常
    checkInterfaceType(type);
    // 尝试从上文提到的 `extensionLoaders` 管理器中获取已有的ExtensionLoader
    ExtensionLoader<T> loader = (ExtensionLoader<T>) extensionLoaders.get(type);

    // 获取失败的话,尝试扫描并加载指定type的扩展,并初始化之
    if (loader == null) {
        loader = initExtensionLoader(type);
    }
    return loader;
}
复制代码

而后看看 initExtensionLoader 方法干了啥。

// synchronized锁控制,防止并发初始化
public static synchronized <T> ExtensionLoader<T> initExtensionLoader(Class<T> type) {
    ExtensionLoader<T> loader = (ExtensionLoader<T>) extensionLoaders.get(type);
    // 二层检查,防止并发问题
    if (loader == null) {
        // type会赋值给实例变量 `type`,并初始化实例变量 `classLoader` 为当前线程上线文的ClassLoader
        loader = new ExtensionLoader<T>(type);
        // 添加到全局管理器 `extensionLoaders` 中
        extensionLoaders.putIfAbsent(type, loader);

        loader = (ExtensionLoader<T>) extensionLoaders.get(type);
    }

    return loader;
}
复制代码

至此,咱们就初始化了 Filter 接口的 ExtensionLoader,并将它托管到了 extensionLoaders 中。

1.3 SPI的扫描和加载以及获取指定的扩展实例

Motan 是懒加载策略,当第一次获取具体的某一扩展实例时,才会扫描和加载全部的扩展实例。

例如,能够经过如下方式获取咱们上面建立的名为 profilerFilter 接口的扩展实例。

// 初始化 Filter 到全局管理器 `extensionLoaders` 中
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class);
Filter profilterFilter = extensionLoader.getExtension("profiler");
复制代码

Filter 接口的 ExtensionLoader 其实是在第一次 extensionLoader.getExtension("profiler") 时完成的。下面具体看一下 getExtension 方法干了啥。

public T getExtension(String name) {
    // Notes:就是经过这个checkInit方法扫描和加载的
    checkInit();
    // .. 暂时省略 ..
}
复制代码

继续研究 checkInit 方法。

private volatile boolean init = false;
private void checkInit() {
    // 用init标记,只初始化一次
    if (!init) {
        loadExtensionClasses();
    }
}

private static final String PREFIX = "META-INF/services/";
private synchronized void loadExtensionClasses() {
    if (init) {
        return;
    }
    // 扫描和加载
    extensionClasses = loadExtensionClasses(PREFIX);
    singletonInstances = new ConcurrentHashMap<String, T>();

    init = true;
}
复制代码

loadExtensionClasses 方法会扫描 META-INF/services/ 下的全部文件,并解析文件内容,它会调用 loadClass 方法,该方法实现以下:

// classNames 就是每一个文件中的具体扩展实现的全限定名称。
private ConcurrentMap<String, Class<T>> loadClass(List<String> classNames) {
    ConcurrentMap<String, Class<T>> map = new ConcurrentHashMap<String, Class<T>>();

    for (String className : classNames) {
        try {
            Class<T> clz;
            if (classLoader == null) {
                clz = (Class<T>) Class.forName(className);
            } else {
                clz = (Class<T>) Class.forName(className, true, classLoader);
            }

            checkExtensionType(clz);
            // 获取 @SpiMeta 注解声明的名称
            String spiName = getSpiName(clz);

            if (map.containsKey(spiName)) {
                failThrows(clz, ":Error spiName already exist " + spiName);
            } else {
                map.put(spiName, clz);
            }
        } catch (Exception e) {
            failLog(type, "Error load spi class", e);
        }
    }

    return map;
}
复制代码

这个方法作的事情就是获取全部合法的扩展的class。最终其返回值会赋值给实例变量 extensionClasses,至此完成了扫描和加载工做。

PS:由上可知,extensionClasses的K-V是具体扩展实现的 @SpiMeta 名称和对应class的映射。以上文的 ProfilerFilter 为例来讲,KEY=profiler,VALUE=ProfilerFilter.class

1.4 获取具体的SPI扩展实现

继续看刚才 getExtension 方法中省略的部分。

public T getExtension(String name) {
    checkInit();

    if (name == null) {
        return null;
    }

    try {
        Spi spi = type.getAnnotation(Spi.class);

        // 获取单例
        if (spi.scope() == Scope.SINGLETON) {
            return getSingletonInstance(name);
        } else {
            // 获取多例
            Class<T> clz = extensionClasses.get(name);

            if (clz == null) {
                return null;
            }

            return clz.newInstance();
        }
    } catch (Exception e) {
        failThrows(type, "Error when getExtension " + name, e);
    }

    return null;
}
复制代码

获取多例的状况很容易,直接从前面加载好的 extensionClasses 中获取,若是获取到就 newInstance() 一个新的实例。

下面看下单例的状况:getSingletonInstance 方法。

private T getSingletonInstance(String name) throws InstantiationException, IllegalAccessException {
    T obj = singletonInstances.get(name);

    if (obj != null) {
        return obj;
    }

    Class<T> clz = extensionClasses.get(name);

    if (clz == null) {
        return null;
    }

    synchronized (singletonInstances) {
        obj = singletonInstances.get(name);
        if (obj != null) {
            return obj;
        }

        obj = clz.newInstance();
        singletonInstances.put(name, obj);
    }

    return obj;
}
复制代码

获取单例时,会优先尝试从单例集合 singletonInstances 中获取,若是获取不到,说明这个单例实例还没添加到单例集合中(或没有对应名称的具体实例),而后会尝试从 extensionClasses 中获取,若是还获取不到,就是真没有了,若是获取到,会new一个instance到单例集合中。

以上就是 Motan 获取具体SPI扩展实现的方式。

以上即是 Motan 的 SPI 机制。


欢迎关注个人微信公众号

公众号
相关文章
相关标签/搜索