Dubbo 源码分析 —【4】SPI 扩展机制 上

本贴最后更新于 2217 天前,其中的信息可能已经事过境迁
  • Dubbo 采用 Microkernel + Plugin 模式,Microkernel 只负责组装 Plugin,Dubbo 自身的功能也是通过扩展点实现的,也就是 Dubbo 的所有功能点都可被用户自定义扩展所替换。
  • 采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。

来源

Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。

JDK 标准的 SPI

详细请参考:Java 中 SPI 机制深入及源码解析

  • 主要类:ServiceLoader 默认从 META-INF/services/ 路径下读取文件

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源

  • 如果扩展点加载失败,连扩展点的名称都拿不到了

部分核心代码

// 判断是否有下一个扩展实现
private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    if (configs == null) {
        try {
            String fullName = PREFIX + service.getName();
            // 根据全路径获取配置的扩展实现
            if (loader == null)
                configs = ClassLoader.getSystemResources(fullName);
            else
                configs = loader.getResources(fullName);
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
    }
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        pending = parse(service, configs.nextElement());
    }
    nextName = pending.next();
    return true;
}
// 获得下一个扩展实现
private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
        // 根据全限定名加载类
        c = Class.forName(cn, false, loader);
    } catch (ClassNotFoundException x) {
        fail(service,
                "Provider " + cn + " not found");
    }
    if (!service.isAssignableFrom(c)) {
        fail(service,
                "Provider " + cn  + " not a subtype");
    }
    try {
        // 判断是否是 SPI 接口的实现,如果是则加入到提供者列表
        S p = service.cast(c.newInstance());
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
                "Provider " + cn + " could not be instantiated",
                x);
    }
    throw new Error();          // This cannot happen
}

DUBBO 加强的 SPI

约定

在扩展类的 jar 包内,放置扩展点配置文件 META-INF/dubbo/ 接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。

示例

以扩展 Dubbo 的协议为例,在协议的实现 jar 包内放置文本文件:META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol,内容为:

xxx=com.alibaba.xxx.XxxProtocol

实现类内容 :

配置模块中的配置

Dubbo 配置模块中,扩展点均有对应配置属性或标签,通过配置指定使用哪个扩展实现。比如:

<dubbo:protocol name="xxx" />

示例:https://www.jianshu.com/p/dc616814ce98
https://www.jianshu.com/p/bc523348f519

源码分析

可参考:Dubbo 中 SPI 扩展机制详解

@SPI 、@Adaptive 、@Activate 作用

  • @SPI (注解在类上) : @SPI 注解标识了接口是一个扩展点 , 属性 value 用来指定默认适配扩展点的名称。
  • @Activate (注解在类型和方法上) : @Activate 注解在扩展点的实现类上 ,表示了一个扩展类被获取到的的条件,符合条件就被获取,不符合条件就不获取 ,根据 @Activate 中的 group 、 value 属性来过滤 。具体参考 ExtensionLoader 中的 getActivateExtension 函数。 可参考 Dubbo SPI 之 Activate 详解
  • @Adaptive (注解在类型和方法上) :
    • 注解在类上 , 这个类就是缺省的适配扩展。
    • 注解在接口的方法上,ExtensionLoader 根据接口定义动态的生成适配器代码,并实例化这个生成的动态类。
    • 可参考 Dubbo SPI 之 Adaptive 详解

核心类 ExtensionLoader

类似与 Java 的 ServiceLoader

最简单使用例子:

ExtensionLoader.getExtensionLoader(CompatibleExt.class).getExtension("impl1")

第一步 getExtensionLoader

根据 SPI 接口创建出一个 ExtensionLoader 实例,如果本地缓存中已存在则使用缓存的,如果不存在则实例化一个,与接口相关联放入缓存。

// 每一个 SPI 扩展有一个对应的加载类
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null)
        throw new IllegalArgumentException("Extension type == null");
    if (!type.isInterface()) {
        throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
    }
    // 没有使用 @SPI 做注解的接口
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException ...
    }
    // 从本地缓存中加载指定 SPI 的加载类
    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;
}
// 构造方法
private ExtensionLoader(Class<?> type) {
    this.type = type;
    // objectFactory是一个 ExtensionFactory 类型的属性,主要用于加载需要注入的类型的实现
    // objectFactory 主要用在注入那一步,详细说明见注入时候的说明
    // getAdaptiveExtension 方法获取一个运行时自适应的扩展类型 详细介绍会在下文
    // 这里记住非 ExtensionFactory 类型的返回的都是一个AdaptiveExtensionFactory
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

第二步 getExtension

根据名称获得扩展实现

// 根据名称获得扩展实现
public T getExtension(String name) {
    if (name == null || name.length() == 0)
        throw new IllegalArgumentException("Extension name == null");

    // 如果 名称是 true ,则获得默认的扩展实现
    if ("true".equals(name)) {
        // 如果 @SPI 指定了默认扩展名 则设置 cachedDefaultName 
        // cachedClasses 存放 指定路径下名称对应的扩展实现
        // 如果 cachedDefaultName 没有设置,则返回 null
        return getDefaultExtension();
    }
    // 从本地缓存中取一个 实例
    // Holder 在多线程中 防并发
    Holder<Object> holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<Object>());
        holder = cachedInstances.get(name);
    }
    // 容器中 取实例 如果存在则返回 不存在则加载创建
    // 使用二次校验 Holder 中使用了 volatile 防止指令重排引起的并发问题
    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) {
    // 根据名称获得扩展实现类 
    // getExtensionClasses  先看下文分析
    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);
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
            for (Class<?> wrapperClass : wrapperClasses) {
                // 使用包装类包装实例 进行注入
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException ...
    }
}

getExtensionClasses 的实现是为了加载指定路径的文件配置

private Map<String, Class<?>> getExtensionClasses() {
    Map<String, Class<?>> classes = cachedClasses.get();
    // 如果本地 缓存中不存在 则初始化 cachedClasses
    // 同样使用的是二次校验实例化 
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

// 加载 SPI 接口 ,获得配置中的 实现
private Map<String, Class<?>> loadExtensionClasses() {
    ...
            //  设置 @ SPI 指定的默认名称
            if (names.length == 1) cachedDefaultName = names[0];
    ...    
    // 加载目录 下的约定文件内容 META-INF/services/  、 META-INF/dubbo/  、 META-INF/dubbo/internal/ 
    // loadDirectory --> loadResource --> loadClass 
    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    ...
    return extensionClasses;
}
//  加载类 
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    // 判断一个类Class1和另一个类Class2是否相同或是另一个类的超类或接口
    // 与 instanceof 不同之处在于 instanceof 第一个参数需要是实例 isAssignableFrom   可以用类判断
    if (!type.isAssignableFrom(clazz)) {
        throw new IllegalStateException ...
    }
    // 是否 被 @Adaptive 注解 被 @Adaptive 注解的实现类只能有一个,否则抛异常
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        if (cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        } else if (!cachedAdaptiveClass.equals(clazz)) {
            throw new IllegalStateException ...
        }
    } else 
    // 是否是包装类, 放入本地缓存
    if (isWrapperClass(clazz)) {
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    } else {
        clazz.getConstructor();
        // 如果配置中没有指定别名,则查看实现类上  @Extension 注解指定的别名 否则抛异常
        if (name == null || name.length() == 0) {
            name = findAnnotationName(clazz);
            if (name.length() == 0) {
                throw new IllegalStateException ...
            }
        }
        //  多个别名分割
        String[] names = NAME_SEPARATOR.split(name);
        if (names != null && names.length > 0) {
            Activate activate = clazz.getAnnotation(Activate.class);
            // 如果含有 @Activate 注解,则放入本地缓存中
            if (activate != null) {
                cachedActivates.put(names[0], activate);
            } else {
                ...
            }
            for (String n : names) {
                // 根据实现类找别名 只能存在一个
                if (!cachedNames.containsKey(clazz)) {
                    cachedNames.put(clazz, n);
                }
                Class<?> c = extensionClasses.get(n);
                if (c == null) {
                    extensionClasses.put(n, clazz);
                } else if (c != clazz) {
                    throw new IllegalStateException ...
                }
            }
        }
    }
}

injectExtension 根据 set 方法注入

private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            for (Method method : instance.getClass().getMethods()) {
                        // 必须是 set 方法
                if (method.getName().startsWith("set")
                        // 方法必须只有一个参数
                        && method.getParameterTypes().length == 1
                        // set 方法的修饰符 必须是 public
                        && Modifier.isPublic(method.getModifiers())) {
                    // 获得参数类型
                    Class<?> pt = method.getParameterTypes()[0];
                    try {
                        // 获得属性的名称
                        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("fail to inject via method " + method.getName()
                                + " of interface " + type.getName() + ": " + e.getMessage(), e);
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

objectFactory.getExtensionobjectFactory 是私有构造方法中实例化的

objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());

具体的方法内容分析,在下一篇

至此关于简单使用分析完毕。

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3454 回帖 • 191 关注
  • Dubbo

    Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,是 [阿里巴巴] SOA 服务化治理方案的核心框架,每天为 2,000+ 个服务提供 3,000,000,000+ 次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。

    60 引用 • 82 回帖 • 604 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...