跳至主要內容

SPI扩展及其源码

Yaien Blog原创大约 7 分钟微服务RPCdubbo

SPI扩展及其源码

dubbo的扩展点实现与JAVA中实现的扩展点类似,但是功能比JAVA提供的会强大一些。 本次文章简单记录Dubbo SPI扩展点的使用以及核心原理,通过对源码的解读学习dubbo spi扩展点的核心实现以及内部的使用。

SPI 扩展点的实现,都是基于接口来实现的,所以扩展点都是通过实现某个接口来进行扩展的。

核心对象(ExtensionLoader)

ExtensionLoader表示某个接口的扩展点加载器,可以用来加载某个扩展点实例。

核心属性

  • extensionInstances:用来缓存某个接口类型所对应的ExtensionLoader实例
  • type:表示当前ExtensionLoader实例是那个接口的扩展点加载器
  • objectFactory:扩展点工厂,可以通过工厂获取某个扩展点的具体对象实例

使用

  • 新建maven工程,并引入dubbo相关依赖
  • 在resource包下创建子包:MEAT-INF.dubbo
  • 在dubbo包下创建扩展点文件,文件名为扩展点的全类名(包含包名和接口名)
  • 在文件内填写扩展点实现类的全类名以及映射关系

当以上步骤都完成以后,在启动类main方法中通过dubbo的扩展点加载器来加载扩展点

    // 获取加载扩展点加载器
    ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
    // 获取扩展点实现
    Car car = extensionLoader.getExtension("red");
    System.out.println(car);

源码

扩展点加载器

getExtensionLoader(Class class);

实现的是通过某个扩展点,获取该扩展点的扩展点加载器

  • 先查询缓存中有没有传入的扩展点的扩展点加载器,有直接返回
  • 校验该扩展点是否有scope应用范围缓存,没有就从@SPI注解中获取,默认是范围是APPLICATION
  • 判断有没有父扩展点控制器,有的话通过父控制器创建

获取扩展点

T getExtension(String name);

    @SuppressWarnings("unchecked")
    public T getExtension(String name, boolean wrap) {
        // 判断扩展点控制器是否已销毁
        checkDestroyed();

        // 参数校验
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }

        // 如果传入的字符串是true,则返回默认的扩展点实例
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        
        String cacheKey = name;
        if (!wrap) {
            cacheKey += "_origin";
        }
        // 获取holder,holder中封装具体的扩展点实例
        final Holder<Object> holder = getOrCreateHolder(cacheKey);
        // 双重检查、加锁创建holder
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                     // 创建扩展点
                    instance = createExtension(name, wrap);
                    // 将扩展点赋值到holder中
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }
  • 传入的name如果是true,则获取默认的扩展点实现,如果没有配则返回null
  • 通过name,获取一个holder,holder中存扩展点实例。先通过缓存获取,没有则创建一个,然后添加到缓存中
  • 获取扩展点实例,如果为null则通过holder进行加锁,并进行双重检查,然后创建扩展点实例

通过holder对象可以看到,里面只有一个value参数,存的就是扩展点实例。 那为什么要将实例封装在holder中再进行缓存,而不是直接缓存具体实例呢?

从上面获取扩展点源码中就可以看出,首先是从缓存中获取holder,然后通过holder加锁去创建扩展点实例,这一步就是为了细化加锁的粒度,不同的扩展点实例只会有一个锁。
如果我们不适用holder包装一下实例,那缓存中获取的就是null,没办法去处理并发下创建扩展点实例的问题了。

创建扩展点(createExtension)

    private T createExtension(String name, boolean wrap) {
        // 通过名称,获取扩展接口class
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null || unacceptableExceptions.contains(name)) {
            throw findException(name);
        }
        try {
            T instance = (T) extensionInstances.get(clazz);
            if (instance == null) {
                // 创建实例
                extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
                instance = (T) extensionInstances.get(clazz);
                // 调用实例化前处理器
                instance = postProcessBeforeInitialization(instance, name);
                // 实例化
                injectExtension(instance);
                //调用实例化后处理器
                instance = postProcessAfterInitialization(instance, name);
            }

            if (wrap) {
                List<Class<?>> wrapperClassesList = new ArrayList<>();
                if (cachedWrapperClasses != null) {
                    wrapperClassesList.addAll(cachedWrapperClasses);
                    wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                    Collections.reverse(wrapperClassesList);
                }

                if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                    for (Class<?> wrapperClass : wrapperClassesList) {
                        Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                        boolean match =
                            (wrapper == null) || (
                                (ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name))
                                    && !ArrayUtils.contains(wrapper.mismatches(), name)
                            );
                        if (match) {
                            // 获取包装类有type类的构造方法,构建包装类
                            instance = injectExtension(
                                (T) wrapperClass.getConstructor(type).newInstance(instance));
                            instance = postProcessAfterInitialization(instance, name);
                        }
                    }
                }
            }

            // Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException(
                "Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(),
                t);
        }
    }
  • 通过传入的name获取扩展点
  • 通过扩展点尝试从缓存中获取具体的实例,没有则创建
  • 调用扩展点实例化前的处理器
  • 实例化
  • 调用扩展点实例化后处理器
  • 如果存在包装器,还要创建包装器,并将实例放到包装器中(AOP)

加载文件

    private Map<String, Class<?>> loadExtensionClasses() throws InterruptedException {
        checkDestroyed();

        // 缓存扩展点的默认实例
        cacheDefaultExtensionName();

        // red=cn.yanggl.RedCar -> <red,RedCar.class>
        Map<String, Class<?>> extensionClasses = new HashMap<>();

        // 从多个目录下加载文件并解析
        for (LoadingStrategy strategy : strategies) {
            loadDirectory(extensionClasses, strategy, type.getName());

            // compatible with old ExtensionFactory
            if (this.type == ExtensionInjector.class) {
                loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());
            }
        }

        return extensionClasses;
    }
  • 从缓存中获取所有扩展点组成的mapper,如果为空的就加锁然后从文件中加载
  • 解析@SPI上标注的默认扩展点,有则进行缓存(@SPI注解上的默认扩展点只能有一个,写了多个就会报错)
  • 编列多个目录下进行加载文件并解析,存到extensionClasses中

解析文件

    private void loadResource(Map<String, Class<?>> extensionClasses,
                              ClassLoader classLoader,
                              java.net.URL resourceURL,
                              boolean overridden,
                              String[] includedPackages,
                              String[] excludedPackages,
                              String[] onlyExtensionClassLoaderPackages) {
        try {
            // 获取新文件内容
            List<String> newContentList = getResourceContent(resourceURL);
            String clazz;
            for (String line : newContentList) {
                try {
                    String name = null;
                    int i = line.indexOf('=');
                    if (i > 0) {
                        name = line.substring(0, i).trim();
                        clazz = line.substring(i + 1).trim();
                    } else {
                        clazz = line;
                    }

                    if (StringUtils.isNotEmpty(clazz)
                        && !isExcluded(clazz, excludedPackages)
                        && isIncluded(clazz, includedPackages)
                        && !isExcludedByClassLoader(clazz, classLoader, onlyExtensionClassLoaderPackages)) {

                        // 加载类,并添加到extensionClasses中
                        loadClass(classLoader,
                            extensionClasses,
                            resourceURL,
                            Class.forName(clazz, true, classLoader),
                            name,
                            overridden);
                    }
                } 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(COMMON_ERROR_LOAD_EXTENSION, "", "",
                "Exception occurred when loading extension class (interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL,
                t);
        }
    }
  • 首先通过文件路径加载内容
  • 便利所有行,并解析行数据,将等号左边作为key,右边的全类路径创建实例作为value存到extensionClasses中
    • 判断类是不是有@Adaptive注解
    • 判断类是不是wrapper包装类(存在一个参数的构造器并且参数==type)
    • 都不是则正常创建(扩展点允许多个别名)

扩展点依赖注入(IOC)

    private T injectExtension(T instance) {
        if (injector == null) {
            return instance;
        }

        try {
            for (Method method : instance.getClass().getMethods()) {
                // 不是set方法,跳过
                if (!isSetter(method)) {
                    continue;
                }
                /**
                 * Check {@link DisableInject} to see if we need auto-injection for this property
                 */
                // set方法上有@DisableInject,跳过
                if (method.isAnnotationPresent(DisableInject.class)) {
                    continue;
                }

                // When spiXXX implements ScopeModelAware, ExtensionAccessorAware,
                // the setXXX of ScopeModelAware and ExtensionAccessorAware does not need to be injected
                if (method.getDeclaringClass() == ScopeModelAware.class) {
                    continue;
                }
                if (instance instanceof ScopeModelAware || instance instanceof ExtensionAccessorAware) {
                    if (ignoredInjectMethodsDesc.contains(ReflectUtils.getDesc(method))) {
                        continue;
                    }
                }

                // 获取set方法的第一个参数类型
                Class<?> pt = method.getParameterTypes()[0];
                // 判断是不是基本类型(String、Integer...)
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }

                try {
                    // 截取set方法名(setCar -> car)
                    String property = getSetterProperty(method);
                    // 获取指定类型和名称的实例
                    Object object = injector.getInstance(pt, property);

                    if (object != null) {
                        method.invoke(instance, object);
                    }
                } catch (Exception e) {
                    logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "",
                        "Failed to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(),
                        e);
                }
            }
        } catch (Exception e) {
            logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "", e.getMessage(), e);
        }
        return instance;
    }
  • 便利所有实例的方法,跳过不是set方法、方法上有@DisableInject、实现ScopeModelAware接口的方法
  • 获取set方法的第一个参数类型,如果是基本数据类型则跳过
  • 截取set方法名(setCar -> car)
  • 通过参数类型和名称创建实例
    • 通过Spring容器获取
    • dubbo自己去生成一个代理类(方法上要加@Adaptive,否则调用方法的时候会报错)

包装器(AOP)

  • 获取文件解析时存的所有包装器,降序排序,然后再颠倒成升序
  • 便利所有的包装器
  • 判断,需要生成实例的条件如下
    • 类上没有@Wrapper
    • 有@Wrapper注解
      • matches为空或者matches包含扩展点名称
      • 并且mismatches不包含扩展点名称
  • 找包装类上有以扩展类类型为参数的构造方法,通过该构造方法创建包装类
  • 传入包装类和扩展类类名,调用初始化后后置处理器
上次编辑于:
贡献者: yanggl