Dubbo 源码分析

风格不统一 提交于 2020-04-06 17:40:38

开篇

前面用了4 篇文章分析了 Dubbo SPI 的几种用法以及如何在 Dubbo 中应用的,

本文通过调试 Dubbo2.7.x 源码分析 如何通过 getExtension(name) 获取一个扩展对象实例

正文

回顾一下 Dubbo SPI 的最基本的用法

public class App 
{
    public static void main( String[] args )
    {
		// 第一步
        ExtensionLoader<HelloService> extensionLoader = ExtensionLoader.getExtensionLoader(HelloService.class);
        // 第二步
        HelloService helloService = extensionLoader.getExtension("helloService");
        // 第三步
        helloService.sayHello("xiaoming");
    }
}

我们直接从第二步 debug 进入getExtension(name)方法

1. ExtensionLoader 的属性

在 debug 进入方法之前, 先来看几个 ExtensionLoader的属性

   
    // 1. 扩展接口, 比如 Protocol
    private final Class<?> type;
    
    // 2. 扩展实现类集合, key 为 Protocol , value 为 DubboProtocol
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
    
    // 3. (缓存的)扩展实现类集合
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

    // 4. 缓存的扩展对象集合 key 为 dubbo, value 为 DubboProtocol
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
 
    // 5. 扩展增强 Wrapper 实现类集合
    private Set<Class<?>> cachedWrapperClasses;

2. ExtensionLoader # getExtension(String name)

此方法大致分为 3 大步:

  • 如果 name 为 "true", 则获取 默认的扩展类对象
  • 否则, 就去取 缓存的扩展类对象
  • 如果缓存中不存在,就去加载并 实例化扩展类 , 并放入缓存

下面就针对上面的 三大步, 逐个分析

    public T getExtension(String name) {
        
        // 省略扩展名非空校验     
        
        // 1. 如果 name 等于 true, 获取默认的扩展对象
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        
        // 2. 从缓存的扩展类对象获取  
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        
        // 缓存中没有对应的实例
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                // 双重校验
                if (instance == null) {
                    // 3. 加载扩展实现类,并实例化
                    instance = createExtension(name);
                    // 扩展对象放入缓存
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

2.1. ExtensionLoader # getDefaultExtension()

   public T getDefaultExtension() {
        // 1.1 获取所有的扩展类
        getExtensionClasses();
        if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
            return null;
        }
        return getExtension(cachedDefaultName);
    }
2.1.1 getExtensionClasses()
   private Map<String, Class<?>> getExtensionClasses() {
        // 先从缓存中取
        // cachedClasses 放置的是缓存的扩展实现类集合
        Map<String, Class<?>> classes = cachedClasses.get();
        // 依然是双重校验+锁的方法去获取
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // 缓存没有命中, 就会去加载 META-INF/dubbo ,META-INF/dubbo/intenal,   ,META-INF/service 目录下去加载
                    classes = loadExtensionClasses();
                    // 加载完成,就放入缓存
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }
2.1.2 loadExtensionClasses()

上面也有提到, 缓存中没有,就会去下面的目录去加载文件, 然后解析文件中的内容

  • META-INF/dubbo
  • META-INF/dubbo/intenal
  • META-INF/service

具体源码就不贴出来了, 文件的内容格式如下:

dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol

2.2. ExtensionLoader # getOrCreateHolder()

    private Holder<Object> getOrCreateHolder(String name) {
        // 跟上面如出一辙, 先从缓存中取,    
        // cachedInstances 放置的是 缓存的扩展类对象
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<>());
            holder = cachedInstances.get(name);
        }
        return holder;
    }

2.3 ExtensionLoader # createExtension(String name)

  • 根据名称(比如: dubbo)获取扩展实现类(DubboProtocol)
  • 从扩展实现类集合中获取扩展实现类对象 instance
  • 如果扩展类对象 instance依赖其他扩展实现类 OtherClass, 就需要把 OtherClass 实例化,并通过 setter 方法注入到instance里面
  • 判断 Protocol 是否有其他增强实现, 比如 ProtocolFilterWrapper等等
    • 如果有, 则把它 ProtocolFilterWrapper实例化, 赋值给 instance
    
    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);
            // 判断是否是 wrapper
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    // 重新赋值成一个 wrapper (增强类)
                    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);
        }
    }

总结

本文主要针对通过 getExtension(name)获取一个扩展对象实例, 来对 Dubbo 的源码进行了剖析, 当然全文只是描述了一个大概的流程, 比如如何解析 SPI 配置文件 就没有深入去讲解.

大体流程如下所示:

  • 如果是默认的扩展对象( "true".equals(name)), 通过 getDefaultExtension()方法获取 默认的扩展接口实现类对象, 并返回
  • 判断缓存 cachedInstances集合中是否存在
    • 如果有, 就从缓存的扩展接口实现类对象获取, 赋值给 instance
    • 如果没有, 就通过 createExtension获取 扩展接口实现类对象
      • 获取扩展实现类 比如 DubboProtocol
      • 判断缓存中是否有扩展接口实现类的对象 instance, 如果没有就把上一步的类DubboProtocol给实例化
      • 判断instance是否依赖其他扩展接口实现类对象 CLassA, CLassB 等等,如果有,需要通过 setter 方法注入进去
      • 判断 DubboProtocol是否有其他增 Wrapper 实现类, 比如 ProtocolFilterWrapper, 如果有, 赋值给上面的instance
      • 最后放入缓存
  • 返回扩展实现类对象 return instance

下一篇文章会针对 获取自适应扩展点实例 getAdaptiveExtension()方法进行源码分析!

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!