Dubbo SPI机制


什么是 SPI

SPI全称是Service provider interface,顾名思义就是服务提供接口,本质上就是以接口的形式提供服务。

SPI目标就是对应用提供可拔插的服务,应用会首先提供一个接口进行扩展。我们只需要在META-INF下建立一个service/接口全限定名 文件。文件内容就是实现类,可以通过编程发现classpath下所有实现的这个接口的类并将它们全部加载到JVM中。

MySQL的驱动包就利用了这个规则,java.sql.Driver是java提供的一个接口。mysql驱动包负责实现这个类,并在service下通过SPI机制试图让Java发现并加载它。所以像之前Class.forName(“xxx”);现在就不必要编写了。

Dubbo的SPI

上面所说的SPI是Java底层提供的功能,但这种SPI的设计与Dubbo所需要的功能有一些偏差,Dubbo就自己实现了一套属于自己的SPI。

具体实现和JavaSPI基本一样,有以下几点不一样

  1. 接口文件包名不一样
  2. 文件中是已key-value存储,不是简单的字符串

调用

ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol protocol = extensionLoader.getExtension("http");
System.out.println(protocol);
  1. 获取ExtensionLoader实例
  2. 通过ExtensionLoader#getExtension方法获取拓展类对象

Dubbo getExtensionLoader源码分析

此方法主要作用是获取ExtensionLoader实例,ExtensionLoader这个实例具是某个接⼝的扩展点加载器,可以⽤来加载某个扩展点实例。

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    // 创建一个缓存
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    // 缓存没命中,设置进缓存,这里可以看出ExtensionLoader是按类型划分
    if (loader == null) {
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

Dubbo getExtension源码分析

public T getExtension(String name) {
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    // 获取默认扩展类
    if ("true".equals(name)) {
        return getDefaultExtension();
    }

    // holder封装,便于加锁
    final Holder<Object> holder = getOrCreateHolder(name);
    Object instance = holder.get();

    // DCL
    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) {
    // 获取SPI接口的所有实现类,并通过name获取指定实现类
    // 这里就是找哪些包下面有SPI接口实现类的地方 ===============重要方法==========
    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);

        // AOP,cachedWrapperClasses无序
        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) {
    }
}

这个方法相当重要,在这其中共有4大逻辑

  1. getExtensionClasses获取拓展类
  2. 创建实例
  3. 依赖注入
  4. Wrapper对象包裹实例

getExtensionClasses获取拓展类

private Map<String, Class<?>> getExtensionClasses() {
   // DCL
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                // 加载、解析文件 Map
                classes = loadExtensionClasses(); 
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

private Map<String, Class<?>> loadExtensionClasses() {
        // cache接口默认的扩展类,就是在接口上标注了@SPI注解的value
        cacheDefaultExtensionName();
        // 加载不同路径下的文件
        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

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
    String fileName = dir + type;
    try {
        // 根据文件中的内容得到urls, 每个url表示一个扩展
        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();
                // 遍历url进行加载,把扩展类添加到extensionClasses中
                // 这个方法主要就是文件内容的读取,并将文件内容每一行数据进行字符串切割提取出name和类全限定名,然后执行loadClass加载类并传入到extensionClasses中
                loadResource(extensionClasses, classLoader, resourceURL);
            }
        }
    } catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " +
                type + ", description file: " + fileName + ").", t);
    }
}

loadResource->loadClass

加载类并传入到extensionClasses中

loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    // native方法,判断此类是不是type的实现类,不是就要报错
    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)) {
        cacheAdaptiveClass(clazz);
    // 判断是不是一个Wrapper类,判断方法是是不是有一个构造方法参数只有一个,类型是接口
    } else if (isWrapperClass(clazz)) {
        // 是一个Wrapper类
        cacheWrapperClass(clazz);
    } else {
        // 需要有无参的构造方法
        clazz.getConstructor();

        // 在文件中没有name,但是在类上指定了Extension的注解上指定了name
        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)) {
            // 缓存一下被Activate注解了的类
            cacheActivateClass(clazz, names[0]);

            // 有多个名字
            for (String n : names) {
                // clazz: name
                cacheName(clazz, n);
                // name: clazz
                saveInExtensionClass(extensionClasses, clazz, n);
            }
        }
    }
}

injectExtension依赖注入

这里的依赖注入和Spring的依赖注入类似

private T injectExtension(T instance) {

    if (objectFactory == null) {
        return instance;
    }

    try {
        for (Method method : instance.getClass().getMethods()) {
            // 判断方法是否有set特性
            if (!isSetter(method)) {
                continue;
            }
        
            // 如果方法上标注了DisableInject注解表示不使用依赖注入
            if (method.getAnnotation(DisableInject.class) != null) {
                continue;
            }

            // 判断set方法中的参数类型是否为基本类型,基本类型就跳过
            Class<?> pt = method.getParameterTypes()[0];
            if (ReflectUtils.isPrimitives(pt)) {
                continue;
            }

            try {
                // 得到setXxx中的xxx
                String property = getSetterProperty(method);

                // 根据参数类型或属性名,从objectFactory中获取到对象,然后调用set方法进行注入
                // 这里的objextFactory 是 AdaptiveExtensionFactory
                Object object = objectFactory.getExtension(pt, property);
                if (object != null) {
                    // 调用set方法注入
                    method.invoke(instance, object);
                }
            } catch (Exception e) {
            }
        }
    } catch (Exception e) {
    }
    return instance;
}


@Override
public <T> T getExtension(Class<T> type, String name) {
    // 遍历两个ExtensionFactory( SpringExtensionFactory,SpiExtensionFactory),从ExtensionFactory中得到实例
    for (ExtensionFactory factory : factories) {
        T extension = factory.getExtension(type, name);
        if (extension != null) {
            return extension;
        }
    }
    return null;
}

factory.getExtension&&createAdaptiveExtensionClass

factory 有2种,一种是SpringExtensionFactory还有一种是SpiExtensionFactory,SpringExtensionFactory很简单就是去Spring容器中获取这里我们具体来看下SpiExtensionFactory是如何进行依赖注入的

SpiExtensionFactory->getAdaptiveExtension->createAdaptiveExtension->getAdaptiveExtensionClass

private Class<?> getAdaptiveExtensionClass() {
    // 获取当前接口的所有扩展类
    getExtensionClasses();
    // 缓存了@Adaptive注解标记的类
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    // 如果某个接口没有手动指定一个Adaptive类,那么就自动生成一个Adaptive类
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

private Class<?> createAdaptiveExtensionClass() {
    // cachedDefaultName表示接口默认的扩展类,,code表示代理类的具体代码
    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);
}

从编译的代码结果中分析,我们发现使用依赖注入会存在一些条件

  1. 必须有参数
  2. 必须在参数中带着Url类型参数

Dubbo中的AOP

在loadResource->loadClass->cacheWrapperClass中缓存了当前type的Wrapper类

Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
    for (Class<?> wrapperClass : wrapperClasses) {
        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
    }
}

这段代码明显可以看出把原实例注入到wrapper方法的构造方法中,并将wrapper对象返回,而且这段代码是遍历的,所以说如果有多个wrapper就会进行多层包裹,假设有2个wrapper就变成了wrapper包裹wrapper包裹原实例


文章作者: dm
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 dm !
评论
 上一篇
SpringBoot与Dubbo整合源码分析 SpringBoot与Dubbo整合源码分析
DemoSpringBoot整合Dubbo只需加个依赖,服务提供者加注解@Service (dubbo的不是spring的,后改为@DubboService),服务消费者加注解@Reference注解 加依赖<!-- Dubbo集成
2023-06-01
下一篇 
Dubbo高级应用 Dubbo高级应用
前言Dubbo作为一个强大的RPC框架,他的功能和特性很多。这里针对其中使用比较多的功能和特性进行测试和记录。 负载均衡官网地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/load
2023-05-01
  目录