Motan 学习笔记(二) - SPI

简介

Motan 使用 SPI 机制实现模块扩展,由于 SPI 本身的特性,增加新功能只需按照扩展机制实现对应的接口即可,不用对原有 Motan 代码进行修改。

关于 spi 机制的更多内容,可以参考 SPI-扩展机制

扩展点

  • Filter

    发送 / 接收请求过程中增加切面逻辑,默认提供日志统计等功能

  • HAStrategy

    扩展可用性策略,默认提供快速失败等策略

  • LoadBalance

    扩展负载均衡策略,默认提供轮询等策略

  • Serialization

    扩展序列化方式,默认使用 Hession 序列化

  • Protocol

    扩展通讯协议,默认使用 Motan 自定义协议

  • Registry

    扩展服务发现机制,默认支持 Zookeeper、Consul 等服务发现机制

  • Transport

    扩展通讯框架,默认使用 Netty 框架

以负载均衡策略为例。

@Spi(scope = Scope.PROTOTYPE)
public interface LoadBalance<T> {...}

@Spi 注解标识扩展加载的形式,单例或多例,这里为多例。

Motan 支持多种负载均衡策略,后续文章再介绍负载均衡本身,本篇不赘述。

使用轮询策略的负载均衡器:

@SpiMeta(name = "roundrobin")
public class RoundRobinLoadBalance<T> extends AbstractLoadBalance<T> {...}

@SpiMeta 注解 name 表示扩展点的名称,根据 name 加载对应扩展。

框架还使用 @Activation 注解提供多扩展加载顺序的支持,sequence 大的排在后面,某些扩展支持。

Demo

这里自定义 Filter 扩展:

@SpiMeta(name = "my")
public class MyFilter implements Filter {
    @Override
    public Response filter(Caller<?> caller, Request request) {
        System.out.println("caller URL: " + caller.getUrl().toString());
        System.out.println("request: " + request.toString());
        return caller.call(request);
    }
}

motan-client.xml 配置文件中增加 filter 配置,使用上面定义的 MyFilter:

<motan:referer filter="my" id="remoteService" interface="com.cdf.motan.rpc.MyMotanServiceAsync" registry="my_zookeeper" requestTimeout="2000"/>

/META-INF/services/ 下新建 com.weibo.api.motan.filter.Filter,内容为 MyFilter 的全类名:

com.cdf.motan.filter.MyFilter

运行 Server 和 Client,查看 Client 输出:

Client 输出

源码提供扩展相关的 Test 类,在 motan-core 包的 src/test/java/core/extension 下。

源码解析

ExtensionLoader

Motan 将扩展方法集成在 com.weibo.api.motan.core.extension.ExtensionLoader 类中,如要编程式获取扩展,可以通过以下方式获取相应名称的实现(以获取 MyFilter 为例):

Filter myFilter = ExtensionLoader.getExtensionLoader(Filter.class).getExtension("my");

getExtensionLoader

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    // 确保是 @Spi 标记的 interface
    checkInterfaceType(type);

    // 首次获取 refer 时加载
    ExtensionLoader<T> loader = (ExtensionLoader<T>) extensionLoaders.get(type);
    if (loader == null) {
        loader = initExtensionLoader(type);
    }
    
    return loader;
}

extensionLoaders 为 ConcurrentMap<Class<?>, ExtensionLoader<?>> 类型,根据接口类型获取持有此扩展所有实现类的 ExtensionLoader。

initExtensionLoader

public static synchronized <T> ExtensionLoader<T> initExtensionLoader(Class<T> type) {
    // double check
    ExtensionLoader<T> loader = (ExtensionLoader<T>) extensionLoaders.get(type);

    if (loader == null) {
        // 实例化一个当前 spi 的 ExtensionLoader
        loader = new ExtensionLoader<T>(type);
        
        // 本类实例持有
        extensionLoaders.putIfAbsent(type, loader);
        
        loader = (ExtensionLoader<T>) extensionLoaders.get(type);
    }

    return loader;
}

getExtension

public T getExtension(String name) {
    // 初次调用初始化
    checkInit();

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

    /*
       获取本 SPI 接口使用的 Scope,根据 Scope 决定:
       从 singletonInstances 拿还是 extensionClasses 用反射实例化一个
     */
    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();
    }
    
    return null;
}

checkInit() 调用 loadExtensionClasses() 方法,后者加锁且 double-check:

loadExtensionClasses

private synchronized void loadExtensionClasses() {
    if (init) {
        return;
    }

    // 从 META-INF/services/ 加载扩展类的 Class,放入 name -> Class 的映射 Map。
    extensionClasses = loadExtensionClasses(PREFIX);
    
    // 实例化 name -> spi实现类对象的 Single Scope 的 Map
    singletonInstances = new ConcurrentHashMap<String, T>();

    init = true;
}

Motan 没有使用 JDK ServiceLoader。

loadExtensionClasses() 调用 loadClass(),使用输入流读取 META-INF/services/ 下指定全类名为名称的文件,按行将配置的实现类全类名进行读取,然后存入 extensionClasses。

源码提供的 Test 类展示了不同 Scope 的区别,具体查看源码,这里不再赘述。

MotanNamespaceHandler

为适配 Spring 配置文件相关命名空间中实现扩展,motan 通过 motan-springsupport 包中的 MotanNamespaceHandler(Spring NamespaceHandlerSupport 子类,参照 注册 BeanDefinition 中 DefaultNamespaceHandlerResolver 的相关内容)向 Spring NamespaceHandlerSupport 类中添加 motan: 命名空间相关 Bean 的 BeanDefinitionParser(MotanBeanDefinitionParser 类型),从而加载相应扩展,查看继承了 NamespaceHandlerSupport 的 MotanNamespaceHandler 类的 init() 方法:

@Override
public void init() {
    registerBeanDefinitionParser("referer", new MotanBeanDefinitionParser(RefererConfigBean.class, false));
    registerBeanDefinitionParser("service", new MotanBeanDefinitionParser(ServiceConfigBean.class, true));
    registerBeanDefinitionParser("protocol", new MotanBeanDefinitionParser(ProtocolConfig.class, true));
    registerBeanDefinitionParser("registry", new MotanBeanDefinitionParser(RegistryConfig.class, true));
    registerBeanDefinitionParser("basicService", new MotanBeanDefinitionParser(BasicServiceInterfaceConfig.class, true));
    registerBeanDefinitionParser("basicReferer", new MotanBeanDefinitionParser(BasicRefererInterfaceConfig.class, true));
    registerBeanDefinitionParser("spi", new MotanBeanDefinitionParser(SpiConfigBean.class, true));
    registerBeanDefinitionParser("annotation", new MotanBeanDefinitionParser(AnnotationBean.class, true));
    Initializable initialization = InitializationFactory.getInitialization();
    initialization.init();
}

可以看到,每个类型都对应构造了 MotanBeanDefinitionParser,其与以下 XML 命名空间相对应:

image-20190219140451227

<motan:method/> 需要定义在 motan:referer 内,用于控制某个函数的行为,故没有单独的 BeanDefinitionParser。

MotanBeanDefinitionParser 构造时传入了相应的 Config Bean 配置类,为了让解析类在解析到配置文件有相应配置时修改覆盖默认配置,配置类在 com.weibo.api.motan.config 目录下,由于本篇介绍 SPI 机制,暂不赘述配置相关的内容。

Demo 中 referer 域的 Filter 扩展,实际是 Referer 使用的 Protocol 实现的 Filter 的过滤链,具体是使用专门为了装配 Filter 的 Protocol 装饰器 -- ProtocolFilterDecorator 的 decorateWithFilter() 方法:

private <T> Referer<T> decorateWithFilter(Referer<T> referer, URL url) {
    List<Filter> filters = getFilters(url, MotanConstants.NODE_TYPE_REFERER);
    ...
}

getFilters() 中还是使用 ExtensionLoader:

private List<Filter> getFilters(URL url, String key) {
    // load default filters
    List<Filter> filters = new ArrayList<Filter>();
    List<Filter> defaultFilters = ExtensionLoader.getExtensionLoader(Filter.class).getExtensions(key);
    if (defaultFilters != null && defaultFilters.size() > 0) {
        filters.addAll(defaultFilters);
    }
    ...
}