dubbo spi机制

想要深入理解dubbo,SPI机制是绕不过去的,面试中也经常被提问到,dubbo大部分组件都是基于SPI机制实现的。

那么什么是SPI机制呢,要解决什么问题呢, 实际上要解决的问题就是,一个接口有多个实现类时,具体实现类不在程序中直接确定,而是由程序之外的配置掌控。这是设计模式中的策略模式。

dubbo SPI是基于java SPI机制实现的,进行一定的改造和优化,dubbo官网对java的spi也有一定的介绍。

那么dubbo 为什么不直接用java的SPI呢,主要概括是三点原因,

1.java可能一次加载所有扩展点,不用的话资源浪费,dubbo改成了按需加载实现类。

2.java spi以com.xxx.xxx  com.xx.xxx格式配置,容易吃掉异常,dubbo改为xx=xxx.xx.xxx键值对配置,容易定位问题。

3.dubbo还增加了 IOC 和 AOP 等特性。

再具体一点,dubbo想要根据url去实现不同的注册中心,协议,负载均衡策略,序列化方式等等。正是由于spi的存在,我们才能在配置中 配置不同的注册中心,负载均衡策略等,我们知道dubbo是以总线模式来时刻传递和保存配置信息的,也就是配置信息都被放在URL上进行传递。然后根据url的参数,dubbo就能动态的去找对应的实现类。

那么比较核心的实现,就是com.alibaba.dubbo.common.extension包下的三个注解@SPI,@Adaptive,@Activate以及核心实现的类ExtensionLoader。

1.扩展点注解@SPI

@SPI注解的作用是标记一个接口为一个扩展点,可以有多个实现,有一个value属性,这个属性通过配置好的键值对匹配,即为默认实现类。该注解只是一个标记,需要跟其他两个注解配合使用。

用注册中心举例,我们知道dubbo支持很多注册中心,如nacos,Multicast,zookeeper,redis等等。官方推荐和我们日常使用的是zookeeper,但通过注解的value可知,其默认实现为"dubbo",根据配置文件可以找到,对应的实现类为DubboRegistryFactory,这是一个dubbo不依赖于其他中间件,自己写的一个注册中心,连dubbo官方文档都没有怎么提到。

2.扩展点自适应注解@Adaptive

@Adaptive这个注解在dubbo框架中一个注解干了2件事情,分别是标记在类级别和标记在方法上,功能并不相同。

 当@Adaptive标记在类级别上时,表名该类是该接口的适配器。例如ExtensionFactory,有三个实现类AdaptiveExtensionFactory,SpiExtensionFactory,SpringExtensionFactory,其中AdaptiveExtensionFactory类标记了@Adaptive,该类则不提供具体实现逻辑,用来选择调用其他两个实现类具体哪种实现。在dubbo框架中在类级别标记的情况较少,@Adaptive主要标记在方法上。

 @Adaptive在方法上标记,可以动态的通过url中参数选择具体实现类。用负载均衡举例子。

 

当我们配置负载均衡策略为roundrobin时,我们请求url中会含有“...&loadbalance=roundrobin&...”,@Adaptive中的“loadbalance”会匹配url中的参数,匹配到对应的参数值为roundrobin,根据配置文件可知对应负载均衡的实现类为RoundRobinLoadBalance,如果我们没有配置策略,则会根据@SPI中的value值RandomLoadBalance.NAME “random”,可知对应实现类为RandomLoadBalance。

再返回过来看@Adaptive的注释,就比较容易好理解,实际上注解的参数可以是一个数组,会依次匹配,如果没匹配到则会用驼峰规则去匹配,如果还没匹配到SPI也没有默认参数,则抛出IllegalStateException。

3.拓展点自动激活注解@Activate

@Activate一般是标记类级别上,主要是使用在多个扩展点实现,根据不同条件被激活的场景中,实际项目中比较实用的就是dubbo过滤器,@Activate有几个重要参数。

group,一般为provider或consumer,表示提供者或消费者调用时触发。

value,如果url中含有该key值,则被触发。

order,排序。

我写一个简单的demo。

接口Filter有@SPI注解,假如有多个filter实现类,可以根据@Activate注解表示,什么时候调用触发,排序等等,这个demo表示在服务端被调用时打印一句话。

4.核心实现类ExtensionLoader

ExtensionLoader是整个拓展机制的核心实现类,其中有三个最重要的方法,getExtension,getAdaptiveExtension,getActivateExtension。

 getExtension

 @SuppressWarnings("unchecked")
    public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

getExtension方法用于获取拓展类实例,基本思路就是检查缓存中是否有现成的数据,没有则调用createExtension去创建。

    @SuppressWarnings("unchecked")
    private T createExtension(String name) {
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

createExtension的创建过程中,先调用getExtensionClasses()方法,也是先去缓存中检查,如果为空,则会加载META-INF/services,META-INF/dubbo,META-INF/dubbo/internal这几个配置的路径,将所有拓展类加载到jvm中,注意此时仅仅是加载class,并没有实例化,最后只初始化所需要的拓展类并放入缓存中,下次再调用时直接从缓存中获取即可。

getAdaptiveExtension

@Adaptive注解的主要功能是根据url的参数,以一定规则去匹配对应的拓展实现类,这里主要记录下实现的手段。

package com.alibaba.dubbo.registry;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class RegistryFactory$Adaptive implements com.alibaba.dubbo.registry.RegistryFactory {
public com.alibaba.dubbo.registry.Registry getRegistry(com.alibaba.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.registry.RegistryFactory) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.registry.RegistryFactory extension = (com.alibaba.dubbo.registry.RegistryFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.registry.RegistryFactory.class).getExtension(extName);
return extension.getRegistry(arg0);
}
}
@SPI("dubbo")
public interface RegistryFactory {

    @Adaptive({"protocol"})
    Registry getRegistry(URL url);

}

以注册工厂为例子,在getAdaptiveExtension中,会动态拼接java代码字符串,生成类名+$Adpative这样的类,再编译成自适应类并初始化,这段java代码是根据RegistryFactory的注解为@SPI("dubbo"),方法注解为 @Adaptive({"protocol"}),拼接成了其中的String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );由此可知,url的protocol参数为空时会用dubbo,否则用url上的protocol参数,最终还是调用的getExtension方法去获取实例。这正是@Adaptive逻辑的实现手段,具体还有很多其他细节可以找对应的源码学习。

getActivateExtension

  public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> activateExtensions = new ArrayList<>();
        List<String> names = values == null ? new ArrayList<>(0) : Arrays.asList(values);
        if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
            getExtensionClasses();
            for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                Object activate = entry.getValue();

                String[] activateGroup, activateValue;

                if (activate instanceof Activate) {
                    activateGroup = ((Activate) activate).group();
                    activateValue = ((Activate) activate).value();
                } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
                    activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
                    activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
                } else {
                    continue;
                }
                if (isMatchGroup(group, activateGroup)
                        && !names.contains(name)
                        && !names.contains(REMOVE_VALUE_PREFIX + name)
                        && isActive(activateValue, url)) {
                    activateExtensions.add(getExtension(name));
                }
            }
            activateExtensions.sort(ActivateComparator.COMPARATOR);
        }

getActivateExtension基本思路也是差不多,通过调用getExtensionClasses()检查缓存,确保加载对应的class在缓存中,遍历拓展类,根据isMatchGroup方法判断满足条件的再进行实例化激活,排序等等。

最后,文章有哪些不对的地方,欢迎给我留言。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值