Dubbo SPI机制源码分析

270 阅读10分钟

通过前面介绍Java SPI技术[。](Java SPI - 掘金 (juejin.cn))我们大概能明白SPI到底是做什么的了那么Dubbo中的功能也是类似的,无非就是以插件的形式进行拓展。接下来我们就看看Dubbo是如何实现的。在Dubbo中以拓展命名哦

2.1.简单使用

先来看一个demo,来了解下如何使用

public class App {
    public static void main(String[] args) {
        ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
        Car bmw = extensionLoader.getExtension("bmw");
        bmw.run();
    }
}

@SPI
public interface Car {
    void run();
}

public class Bmw implements Car{
    @Override
    public void run() {
        System.out.println("run...");
    }
}

这是一个最基本的Dubbo SPI的使用,看着是不是和Java的SPI机制很相似,同样会有一个抽象的接口,同样会有实现,同样会用一个配置文件,只是这里使用的不是ServiceLoader而是使用的ExtensionLoader。

了解了基本的使用方法,我们就可以去分析下源码了。看看Dubbo是如何加载和查找对应的拓展

2.2.加载和查找拓展

2.2.1.ExtensionLoader.getExtensionLoader

这里说明要使用Dubbo的SPI接口上必须要有@SPI注解。再就是每种Class都有自己的ExtentionLoader对象,第一次创建之后会将其放到缓存中来提升性能。到了这里我们就成功的获取到了ExtensionLoader对象。

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    // ...
    // 这里会对参数进行非空校验 && 并且校验参数是一个接口类型 && 类上必须要有@SPI注解

    // ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
    // 这里会去缓存中获取属于这个Class类的ExtentionLoader
    // 如果没有获取到则创建一个新的,并放入到缓存中
    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;
}

2.2.2.ExtensionLoader.getExtension

这部分代码实际上就是Dubbo根据我们要获取的拓展的名称,加载指定目录下的配置文件然后进行加载解析实例化对应的拓展实现类。最后返回回来

public T getExtension(String name) {
    // ...非空校验
    // ... 当name = "ture"的时候返回默认的实现类
    
    // double check
    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;
}

这里我们继续深入,去看看是如何创建的

private T createExtension(String name) {
    // 首先这里会去加载对应目录下配置文件并创建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);
        }
        // 依赖注入 这里后面会细说 先忽略 有个印象就好
        injectExtension(instance);
        // Wrapper 这里后面会细说 先忽略 有个印象就好
        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) {
        throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                                        type + ") couldn't be instantiated: " + t.getMessage(), t);
    }
}

这里我们可以看到dubbo会扫描三个目录下的配置文件:

  • META-INF/services/
  • META-INF/dubbo/
  • META-INF/dubbo/internal/
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

private Map<String, Class<?>> getExtensionClasses() {
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                // 也就是这里加载的
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

/**
    从下面的方法可以看出来,这里会从3个路径下去加载对应的配置文件
    并根据对应的配置文件去找到要加载的class类
*/
private Map<String, Class<?>> loadExtensionClasses() {
    cacheDefaultExtensionName();

    /**
        private static final String SERVICES_DIRECTORY = "META-INF/services/";

        private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";

        private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
    */
    
    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;
}

再看下loadDirectory方法,我们还不知道extensionClasses这个map存的都是些什么东西呢

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
    // 组装文件名,eg:META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory
    String fileName = dir + type;
    try {
        Enumeration<java.net.URL> urls;
        // 获取类加载器
        ClassLoader classLoader = findClassLoader();
        if (classLoader != null) {
            urls = classLoader.getResources(fileName);
        } else {
            urls = ClassLoader.getSystemResources(fileName);
        }
        if (urls != null) {
            while (urls.hasMoreElements()) {
                java.net.URL resourceURL = urls.nextElement();
                // 重点看这里
                loadResource(extensionClasses, classLoader, resourceURL);
            }
        }
    } catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " +
                     type + ", description file: " + fileName + ").", t);
    }
}

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
    try {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                final int ci = line.indexOf('#');
                if (ci >= 0) {
                    line = line.substring(0, ci);
                }
                line = line.trim();
                if (line.length() > 0) {
                    try {
                        String name = null;
                        int i = line.indexOf('=');
                        if (i > 0) {
                            // 等号左边的key值
                            name = line.substring(0, i).trim();
                            // 等号右边的value,也就是全限定名 
                            line = line.substring(i + 1).trim();
                        }
                        if (line.length() > 0) {
                            // 这里将配置文件中的类实例化了,继续看
                            loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                        }
                    } catch (Throwable t) {
                        IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                        exceptions.put(line, e);
                    }
                }
            }
        }
    } catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " +
                     type + ", class file: " + resourceURL + ") in " + resourceURL, t);
    }
}

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    // 校验是否为接口的子类
    if (!type.isAssignableFrom(clazz)) {
        throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                                        type + ", class line: " + clazz.getName() + "), class "
                                        + clazz.getName() + " is not subtype of interface.");
    }
    // 如果类上有Adaptive注解那么缓存下来,
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        // 缓存AdaptiveClass(这里在适配器模式下会有应用,到时候可以回头来看这里)
        cacheAdaptiveClass(clazz);
    } else if (isWrapperClass(clazz)) {
        // 这里满足isWrapperClass的条件是有一个参数的构造器,并且参数为clazz类型的
        // 缓存WrapperClass
        cacheWrapperClass(clazz);
    } else {
        // 默认处理流程,无参构造
        clazz.getConstructor();
        if (StringUtils.isEmpty(name)) {
            // 获取拓展的名字
            name = findAnnotationName(clazz);
            if (name.length() == 0) {
                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
            }
        }

        String[] names = NAME_SEPARATOR.split(name);
        if (ArrayUtils.isNotEmpty(names)) {
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
                cacheName(clazz, n);
                // 到这里就是保存map的方法了
                saveInExtensionClass(extensionClasses, clazz, name);
            }
        }
    }
}

private void saveInExtensionClass(Map<String, Class<?>> extensionClasses, Class<?> clazz, String name) {
    Class<?> c = extensionClasses.get(name);
    if (c == null) {
        // 由此可以看出来,map的key为拓展名称,value为具体的拓展类实现class
        extensionClasses.put(name, clazz);
    } else if (c != clazz) {
        throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + name + " on " + c.getName() + " and " + clazz.getName());
    }
}


/**
  * cache Adaptive class which is annotated with <code>Adaptive</code>
*/
private volatile Class<?> cachedAdaptiveClass = null;

private void cacheAdaptiveClass(Class<?> clazz) {
    if (cachedAdaptiveClass == null) {
        cachedAdaptiveClass = clazz;
    } else if (!cachedAdaptiveClass.equals(clazz)) {
        throw new IllegalStateException("More than 1 adaptive class found: "
                                        + cachedAdaptiveClass.getClass().getName()
                                        + ", " + clazz.getClass().getName());
    }
}

实际上到这里流程就结束了,大家发没发现dubbo的配置文件是k-v形式的,这也就解决了Java SPI不管用没用到某个拓展都会全部加载的问题。

最基本的源码分析完了,接下来我们分别分析下Dubbo的适配器模式、依赖注入以及Wrapper。

2.3.适配器模式

下面这段代码是在ServiceConfig中的一个成员变量。我们就以这里为起点进行分析

private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

2.3.1.ExtensionLoader.getAdaptiveExtension

通过下面的代码我们可以发现getAdaptiveExtension方法实际上就是创建适配器类,他会首先看有没有之前加载的用@Adaptive注解修饰的类,如果存在那么这个用注解修饰的类就是适配器类。否则就会继续执行其他分支

public T getAdaptiveExtension() {
    // 缓存
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
        if (createAdaptiveInstanceError == null) {
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        // 重点在这里
                        instance = createAdaptiveExtension();
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        createAdaptiveInstanceError = t;
                        throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                    }
                }
            }
        } else {
            throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
        }
    }

    return (T) instance;
}

private T createAdaptiveExtension() {
    try {
        // 这里我们要关注getAdaptiveExtensionClass方法,newInstance实际上就是创建实例
        // 最外层的injectExtension方法是提供依赖注入功能的,后续会分析
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
    }
}

private Class<?> getAdaptiveExtensionClass() {
    // 这个方法在前面我们分析过,创建Class
    getExtensionClasses();
    // 这里也与上面的分析对上了,当类上存在@Adaptive注解的时候会放入到这个缓存中
    // 这也就说明如果是该注解标注的类,那么直接返回该类本身!
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    // 这里会动态生成一个新的类
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

private Class<?> createAdaptiveExtensionClass() {
    // 这里就是生成的类的字符串,下面会贴出来
    String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
    ClassLoader classLoader = findClassLoader();
    org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

下面的这段代码就是动态生成的一个类,我们可以看到这里面为我们的方法拼装了一些默认的逻辑

其实就是提供了一种可以在运行时根据传的不同URL来去走不同的实现,当然这里要有个前提是使用@Adaptive注解标注在方法上

package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;


public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
    
    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
        // 参数校验
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg1;
        // 通过URL参数中的protocol字段判断具体要获取哪个实现,默认为dubbo
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
        // 这里就是最普通的根据拓展名称去获取对应的拓展
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
    
    public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
    
    public void destroy()  {
        throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }
    
    public int getDefaultPort()  {
        throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }
}

2.4.依赖注入

下面我们来看一下Dubbo SPI提供的依赖注入功能,Dubbo的依赖注入功能实现的比较简单,它只支持set注入,我们主要看下injectExtension方法就会明白

private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            for (Method method : instance.getClass().getMethods()) {
                // 这里判断了是否存在有名字为set开头的方法,并且参数只有一个,并且是public的
                if (isSetter(method)) {
                    
                    // 在有DisableInject注解标注的方法不需要依赖注入
                    if (method.getAnnotation(DisableInject.class) != null) {
                        continue;
                    }
                    Class<?> pt = method.getParameterTypes()[0];
                    if (ReflectUtils.isPrimitives(pt)) {
                        continue;
                    }
                    try {
                        // 获取要注入的属性名
                        String property = getSetterProperty(method);
                        // 查找依赖
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            // 进行注入,实际上就是执行这个set方法
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("Failed to inject via method " + method.getName()
                                     + " of interface " + type.getName() + ": " + e.getMessage(), e);
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

通过下图我们可以看到Dubbo会从两个途径来进行依赖查找,一种是在SPI中查找,一种是在Spring容器中查找

2.5.Wrapper

Wrapper,是一个包装类,通过对原有类的进行包装,来实现一个类似AOP的功能。感觉很像代理模式。

这里可以先看下loadClass方法是如何加载这些Class的,下面就截取部分关键逻辑了

// 如果是包装类,那么就缓存到cachedWrapperClasses中
if (isWrapperClass(clazz)) {
    cacheWrapperClass(clazz);
} 

// 当有一个参数的构造器,并且参数类型是要拓展的接口类型的就是WrapperClass
private boolean isWrapperClass(Class<?> clazz) {
    try {
        clazz.getConstructor(type);
        return true;
    } catch (NoSuchMethodException e) {
        return false;
    }
}

接着看createExtension方法中关于AOP的片段,其实这里就是对wrapper做一个嵌套,通过有参构造将多个wrapper一层一层的嵌套,最终返回的instance是最外层的一个wrapper类。

// 这里就是拿到上面缓存的Class
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
    for (Class<?> wrapperClass : wrapperClasses) {
        // 1.将wrapper类进行实例化
        // 2.将实例化好的wrapper实例通过有参构造set到下一个要实例化的wrapper中
        // 3.对wrapper类进行依赖注入
        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
    }
}

下面在dubbo中的源码找一个例子来看下,那样就好理解了。

这里我们看下ProtocolFilterWrapper这个类,它是一个wrapper类。并且它有一个成员变量,实际上这个变量就是通过有参构造给赋值的,也就是里层的wrapper类

那么我们看在调用接口的时候,因为返回的是最外层的wrapper类,我们假设ProtocolFilterWrapper就是最外层的wrapper。这是我们调用export方法的时候,可以在这个方法里调用里层的wrapper的export方法。那么这个时候我们就可以在这个调用里层export方法的前后加上我们需要额外处理的逻辑。这实际上不就是AOP嘛。

2.6.总结

这里我们看下Dubbo SPI都主要有哪些功能

  1. 首先提供了与Java SPI类似的功能,通过以插件的形式来进行更好的拓展
  2. Dubbo提供了适配器模式。使用层面主要依赖@Adaptive注解,这个注解可以标注在类上,也可以标注在方法上。
    • 当标注在类上的时候,当调用getAdaptiveExtension方法的时候会直接返回该类
    • 当标注在方法上的时候,会动态生成一个Adaptive类,在标注的方法上做了一些装饰。使其拥有可以根据URL动态选择实现的能力
  1. 提供了依赖注入的能力。这里主要是提供了set注入
  2. 提供了类似AOP的能力,他的实现方式是通过wrapper类层层嵌套,最终形成一个调用链。