美文网首页
dubbo源码解析之SPI扩展机制(一)

dubbo源码解析之SPI扩展机制(一)

作者: binecy | 来源:发表于2019-03-19 20:43 被阅读0次

源码分析基于dubbo 2.7.1-release

看dubbo源码不得不了解dubbo spi机制,因为你常常看到如下代码,而debug又不知所踪

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

dubbo spi机制增强了Java 原生的 SPI 机制。简单来说,ExtensionLoader支持某个扩展接口存在多个扩展类(如Protocol,ProxyFactory),并通过键值对的方式进行配置,dubbo优先使用用户配置扩展类,如果用户没有配置,就使用默认扩展类。

先看看ExtensionLoader.getExtensionLoader

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    ...
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {  // 初始化
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

比较简单,就是从缓存中获取,获取失败则创建一个ExtensionLoader。

再看看getAdaptiveExtension,同样查询缓存,缓存查询失败则调用createAdaptiveExtension生成一个扩展类Extension

private T createAdaptiveExtension() {
    try {
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        ...
    }
}

injectExtension负责调用扩展类Extension的set方法,注入对应属性。
getAdaptiveExtensionClass获取扩展类的Class,再通过newInstance实例化一个对象。

查看getAdaptiveExtensionClass方法

private Class<?> getAdaptiveExtensionClass() {
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {  // 检查缓存
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

getExtensionClasses会检查cachedClasses,如果cachedClasses为空,它会调用loadExtensionClasses,加载用户配置的扩展类,并存放到cachedClasses中。

private Map<String, Class<?>> loadExtensionClasses() {
    ...
        Map<String, Class<?>> extensionClasses = new HashMap<>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
}

变量DUBBO_DIRECTORY的值为META-INF/dubbo/,我们自定义的扩展配置就是放到该路径下。
type.getName().replace("org.apache", "com.alibaba")是因为dubbo成为apace项目后,修改pageage。
loadDirectory会读取指定目录下的配置文件,加载class后存放到cachedClasses。
如dubbo-rpc\dubbo-rpc-dubbo\src\main\resources\META-INF\dubbo\internal\org.apache.dubbo.rpc.Protocol
内容为:

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

cachedClasses中的map中会添加dubbo:Class<DubboProtocol>的键值对。

createAdaptiveExtensionClass会生成一个通用的ExtensionClass

private Class<?> createAdaptiveExtensionClass() {
    // 生成代码字符串
    String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
    ClassLoader classLoader = findClassLoader();
    // 编译成class
    com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    // 编译为Class
    return compiler.compile(code, classLoader);
}

AdaptiveClassCodeGenerator会拼凑生成代码字符串,并通过Compiler编译成class。

AdaptiveClassCodeGenerator这里不深入,主要是通过接口的@SPI,@Adaptive注解获取对应的默认配置。
如Protocol接口

@SPI("dubbo")
public interface Protocol {
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    ...
}

生成的代码如下

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
    ...
}

关键代码是String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ),这里通过invoker.url中的protocol,再通过getExtension方法找到对应的实现类。如dubbo://...registry://...分别对应DubboProtocolRegistryProtocol
默认使用dubbo协议,这个默认值正是从Protocol接口的@SPI("dubbo")注解中获取

这是一个代理类,当我们调用该类的export方法,会通过ExtensionLoader.getExtension获取真正逻辑类,并调用逻辑类export方法。

最后看看getExtension

public T getExtension(String name) {
    ...
    // 获取Holder
    Holder<Object> holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<Object>());
        holder = cachedInstances.get(name);
    }
    // 获取instance 
    Object instance = holder.get();
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                instance = createExtension(name);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

最后看看createExtension

private T createExtension(String name) {
    // 获取ExtensionClass
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        // 初始化ExtensionClass
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        // 注入属性
        injectExtension(instance);
        // 获取wrapperClasse
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && wrapperClasses.size() > 0) {
            for (Class<?> wrapperClass : wrapperClasses) {
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                type + ")  could not be instantiated: " + t.getMessage(), t);
    }
}

getExtensionClasses会查询cachedClasses,前面已经将配置中的Extension信息添加到这里了。

这里要注意,如果存在wrapperClasses,会创建对应的wrapperClasses来装饰目标类。wrapperClasses就是典型的装饰模式。
如Protocol就有ProtocolListenerWrapper,ProtocolFilterWrapper,QosProtocolWrapper等装饰类。

ExtensionLoader加载扩展类时,如果一个扩展类有一个构造方法,参数是它的扩展接口,就会加入到cachedWrapperClasses中。

相关文章

  • JAVA SPI解析

    JAVA SPI解析 在阅读Dubbo源码时发现Dubbo针对java的spi机制做了扩展。那么spi究竟是什么呢...

  • dubbo的spi机制

    dubbo的spi机制 dubbo的扩展点加载机制源自于java的spi扩展机制。那么,何为java的spi扩展机...

  • dubbo源码解析之SPI扩展机制(一)

    源码分析基于dubbo 2.7.1-release 看dubbo源码不得不了解dubbo spi机制,因为你常常看...

  • Dubbo Spring

    Dubbo Spring 解析 dubbo的spi机制是如何管理dubbo的bean和如何进行扩展的基础。那么du...

  • Dubbo SPI机制分析【二】

    title: Dubbo SPI机制分析【二】tags: Dubbo,SPI,源码grammar_cjkRuby:...

  • Dubbo SPI机制分析【一】

    title: Dubbo SPI机制分析tags: Dubbo,SPI,源码grammar_cjkRuby: tr...

  • Dubbo SPI 机制解析

    从上一篇 Java SPI 机制解析 可以知道 Java SPI 的一些劣势。Dubbo 的扩展点加载从 Java...

  • dubbo spi机制源码阅读

    dubbo的扩展能力很强大。他是通过扩展Java的spi机制得到的。 Java Spi机制介绍 SPI是Servi...

  • 🍎 Dubbo SPI 之 Adaptive 自适应类

    翻看 Dubbo 的源码,不难发现,框架到处都在用 SPI 机制进行扩展。这是由于 Dubbo 框架对各种层做了很...

  • dubbo中的SPI扩展机制

    dubbo官网对其SPI扩展机制的介绍如下: Dubbo 的扩展点加载从 JDK 标准的 SPI (Service...

网友评论

      本文标题:dubbo源码解析之SPI扩展机制(一)

      本文链接:https://www.haomeiwen.com/subject/whsbmqtx.html