分析Dubbo中的扩展机制

在dubbo框架中,Extension扩展机制可谓是一大设计亮点,此扩展机制贯穿了dubbo源码,几乎很多地方都有用到,例如配合dubbo的URL机制去动态选择类,可以做到在配置中动态选择,比如在dubbo中的registry,可以选择zk,可以选择redis,只需要在配置文件中替换一下字符串即可达到注册中心的变更。所以了解dubbo中的扩展机制是很有必要的。

Dubbo SPI扩展机制

示例

首先先做一个demo来演示,什么叫SPI扩展机制。

首先定义一个接口,作为SPI的扩展机制接口:

//注意要打上SPI注解,表示这个是扩展点,在后面的源码分析也会提到
@SPI
public interface ITestService {
   
    String sayHello(String word);
}

做两个接口的实现类:

public class TestServiceImpl implements ITestService {
   
    @Override
    public String sayHello(String word) {
   
        return "hello " + word;
    }
}
public class Test2ServiceImpl implements ITestService {
   
    @Override
    public String sayHello(String word) {
   
        return "hello2 " + word;
    }
}

接着在resource目录下,创建一个文件夹叫META-INF/dubbo(文件夹路径有三个可选,在后面源码分析中会再次提到),在此目录下创建文件,文件名为扩展点接口的全类名
在这里插入图片描述
文件内容为各个扩展点的具体类全限定类名

test=org.apache.dubbo.demo.consumer.service.TestServiceImpl
test2=org.apache.dubbo.demo.consumer.service.Test2ServiceImpl

这样就大功告成了,写一个测试类测试一下:

public static void main(String[] args) {
   
  ITestService service = ExtensionLoader.getExtensionLoader(ITestService.class).getExtension("test");
  System.out.println(service.sayHello("world"));
  ITestService service2 = ExtensionLoader.getExtensionLoader(ITestService.class).getExtension("test2");
  System.out.println(service2.sayHello("world"));
}

控制台打印:
在这里插入图片描述
这就是dubbo中的SPI机制,接下来分析其源码了解底层做了什么

源码分析

从示例中我们可以知道,是从ExtensionLoader这样一个类似工厂的类调用getExtensionLoader的API,做了一个扩展点Extension,再从中调用getExtension,传入指定名字即可获取到指定的扩展点,由此作为一个源码入口来看:

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
   
 	  ...
    //查看是否有缓存此扩展点类型的Loader
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
  if (loader == null) {
   
    //new一个ExtensionLoader,传入Class对象作为构造器参数
    EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
    loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
  }
  return loader;
}

这段代码没什么好讲的,只是new了一个对象,将扩展点类型Class对象作为参数传入对象中,作为ExtensionLoader的变量type而已。接下来看看ExtensionLoader.getExtension的逻辑:

public T getExtension(String name) {
   
  if (StringUtils.isEmpty(name)) {
   
    throw new IllegalArgumentException("Extension name == null");
  }
  //如果传入的name字符串为"true"
  if ("true".equals(name)) {
   
    //获取默认的Extension
    return getDefaultExtension();
  }
  //dubbo将实际扩展类封装在Holder对象里
  Holder<Object> holder = getOrCreateHolder(name);
  //查看缓存中是否有此实例
  Object instance = holder.get();
  if (instance == null) {
   
    //double-check,确保线程安全,接下来将会有许多这种操作,不多赘述
    synchronized (holder) {
   
      instance = holder.get();
      if (instance == null) {
   
        //创建一个扩展点,关键入口
        instance = createExtension(name);
        //放入Holder
        holder.set(instance);
      }
    }
  }
  return (T) instance;
}

这里需要关注的点是获取默认的扩展点和创建一个扩展点实例,首先先粗略讲解一下如何获取默认的扩展点,在之后会详细讲到,此时只需要记住cachedDefaultName这个变量:

public T getDefaultExtension() {
   
  //此方法为load所有扩展点的关键方法
  getExtensionClasses();
  //cachedDefaultName变量即为默认扩展点的名称
  if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
   
    return null;
  }
  //拿到默认扩展点名称后,再次获取一次
  return getExtension(cachedDefaultName);
}

这个方法主要是去拿到默认扩展点名称,然后类似递归一样再次去根据此名称获取扩展点类。那么是怎么获取到默认扩展点名称呢?在接下来的load所有扩展点的方法里,会判断@SPI中的value值,此值即为默认的扩展点名称,将会给cachedDefaultName赋值,所以这里可以拿到此名称并返回一个默认的扩展点类。所以扩展点接口上的@SPI注解里的值,都是一个默认的扩展点名称,可以通过调用api,获取默认扩展点,或者传入name=“true”。

接下来回到主线,createExtension创建扩展点方法:

private T createExtension(String name) {
   
  //和上面一样,load所有扩展点的方法
  //此方法返回一个Map,key:name,value:class
  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);
    }
    //IOC注入
    injectExtension(instance);
    Set<Class<?>> wrapperClasses = cachedWrapperClasses;
    //包装类装饰
    if (CollectionUtils.isNotEmpty(wrapperClasses)) {
   
      for (Class<?> wrapperClass : wrapperClasses) {
   
        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
      }
    }
    return instance;
  } catch (Throwable t) {
   
    ...
  }
}

和之前默认扩展点创建一样,在开头都会调用getExtensionClasses方法去寻找所有的扩展点。

其中,有几个关键点分支,需要读者记住:

  1. IOC注入:dubbo中有一个ExtensionFactory类,就像一个IOC容器一样,injectExtension方法可以将扩展点中需要注入的依赖去ExtensionFactory中找到,并给扩展点赋值,关于注入依赖,会在下面讲解,这里主要关注如何找到并创建扩展点。
  2. 包装类装饰:这里需要读者记住cachedWrapperClasses这个变量,从代码中可以看出,如果这个变量不为空,则会遍历它,拿到具体Class对象的构造器,将我们刚刚反射创建的扩展点包装进去,并进行IOC注入依赖。这里读者有个印象即可,在下面单独开一个章节讲解。

这里还是关注我们的主线,getExtensionClasses寻找扩展点:

private Map<String, Class<?>> getExtensionClasses() {
   
  //缓存
  Map<String, Class<?>> classes = cachedClasses.get();
  if (classes == null) {
   
    synchronized (cachedClasses) {
   
      //double-check
      classes = cachedClasses.get();
      if (classes == null) {
   
        //关键方法,获取所有扩展点
        classes = loadExtensionClasses();
        cachedClasses.set(classes);
      }
    }
  }
  return classes;
}

进入关键方法loadExtensionClasses:

private Map<String, Class<?>> loadExtensionClasses() {
   
  //获取扩展点上的@SPI注解,缓存默认扩展点名称
  cacheDefaultExtensionName();

  //前面也有提到,所有扩展点都会存放在一个Map中返回
  Map<String, Class<?>> extensionClasses = new HashMap<>();
  //下面从3个文件夹中,加载扩展点
  loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName()
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值