美文网首页
Dubbo SPI原理详解

Dubbo SPI原理详解

作者: skipper_shou | 来源:发表于2020-11-03 21:24 被阅读0次

SPI全称为Service Provider Interface,是一种服务提供机制,相对于接口调用而言,可以让调用方知道服务提供方具体提供的是什么服务。

jdk与dubbo SPI区别

  • 存储路径
    jdk主要是在项目的META-INF/services目录下以接口的全路径名为文件名,然后在该文件中声明该接口的具体实现类有哪些.
    dubbo 主要包含META-INF/dubbo/external/META-INF/dubbo/internal/META-INF/dubbo/META-INF/services/四种文件路径全路径名为文件夹,然后在该文件中声明该接口的具体实现类有哪些.
  • 接口提供服务数量
    jdk的SPI默认会将所有目标文件中定义的所有子类都读取到返回使用,所以尽量只有每个接口提供一个服务。
    dubbo对此没有限制,通过多种方式获取对应的服务。
  • 接口提供服务
    当提供多个服务时,jdk的SPI无法动态的根据配置来使用不同的配置,而dubbo可以。

dubbo的SPI实现

dubbo对于SPI的实现主要是在ExtensionLoader这个类中,这个类主要有三个方法:

public T getExtension(String name);
public T getAdaptiveExtension();
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type);

1.getExtension(String name)

主要用于获取名称为name的对应的子类的对象

    public T getExtension(String name) {
        return getExtension(name, true);
    }

    public T getExtension(String name, boolean wrap) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        //缓存中获取拓展类
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name, wrap);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

第一段代码可以看出,如果传入的name为true,则获取默认配置的扩展实现,
反之,当缓存不存在时,则进行初始化创建,这里都会调用方法createExtension(String name, boolean wrap)

    public T getDefaultExtension() {
        getExtensionClasses();
        if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
            return null;
        }
        return getExtension(cachedDefaultName);
    }
    private T createExtension(String name, boolean wrap) {
        Class<?> clazz = getExtensionClasses().get(name);
        ......
    }
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;
    }
    private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();

        for (LoadingStrategy strategy : strategies) {
            loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
            loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
        }

        return extensionClasses;
    }

由上面代码可知,首先会初始化默认扩展,具体实现是@SPI注解中是否有value参数,value参数即为默认扩展,然后,会加载对应文件路径下的所有配置放入map中,这里有个知识点,就是通过JDK自带的SPI扩展的方式,获取到四种全部文件路径

而且上述 代码可知,如果刚开始入参为true时,会设置默认扩展的name,再将该name传入getExtension(String name);

回来继续看getExtension(String name);传入参数不为true时的情况

private T createExtension(String name, boolean wrap) {
        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);


            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);
                        if (wrapper == null
                                || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                        }
                    }
                }
            }

            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

由上述代码可知,获取到对应的实例之后,会对扩展进行注入,这里主要是为了做到一个实现类里需要依赖另一个 @SPI 接口的实例
同时做了以下事情:

    1. 先根据name来得到对应的扩展类。从ClassPath下META-INF文件夹下读取扩展点配置文件。
    1. 使用反射创建一个扩展类的实例
    1. 对扩展类实例的属性进行依赖注入,即IOC。
    1. 如果有wrapper,添加wrapper。即AOP。

Dubbo SPI高级用法之自动装配

private T injectExtension(T instance) {

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

        try {
            for (Method method : instance.getClass().getMethods()) {
                //如果有一个方法以set开头,入参长度为1
                if (!isSetter(method)) {
                    continue;
                }
                /**
                 * Check {@link DisableInject} to see if we need auto injection for this property
                 */
                if (method.getAnnotation(DisableInject.class) != null) {
                    continue;
                }
                Class<?> pt = method.getParameterTypes()[0];
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }

                try {
                    //拿到入参方法set后面的字符串,首字母小写
                    String property = getSetterProperty(method);
                    //先拿property的Extension
                    Object object = objectFactory.getExtension(pt, property);
                    if (object != null) {
                        //依赖注入
                        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;
    }

由源码可知,针对方法以set开头,参数只有一个,且没有加入DisableInject注解时,进行依赖注入。
这里会发现有objectFactory.getExtension(pt, property),此处的objectFactory创建ExtensionLoader时被初始化,并获得自适应扩展。
在这里,我们可以知道,dubbo会为每一个扩展创建一个自适应实例。如果扩展类上有@Adaptive,会使用该类作为自适应类。如果没有,Dubbo会为我们创建一个,即AdaptiveExtensionLoader会遍历所有的ExtensionFactory实现,尝试着去加载扩展。如果找到了,返回。如果没有,在下一个ExtensionFactory中继续找。

同时,在createExtension方法中,我们看到了wrapper类。
其实就是典型的装饰者模式,当类中包含根据接口类为入参的构造函数时,我们可以称之为wrapper类,典型的如ProtocolFilterWrapperProtocolListenerWrapper等。
在加载扩展配置loadDirectory时,会根据加载的类,判断为wrapper时,加入到缓存cachedWrapperClasses中。
针对wrapper的接口类再进行依赖注入

2.getAdaptiveExtension()

前面讲到过,Dubbo需要在运行时根据方法参数来决定该使用哪个扩展,所以有了扩展点自适应实例。其实是一个扩展点的代理,将扩展的选择从Dubbo启动时,延迟到RPC调用时。Dubbo中每一个扩展点都有一个自适应类,如果没有显式提供,Dubbo会自动为我们创建一个,默认使用Javaassist。

  public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError != null) {
                throw new IllegalStateException("Failed to create adaptive instance: " +
                        createAdaptiveInstanceError.toString(),
                        createAdaptiveInstanceError);
            }

            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);
                    }
                }
            }
        }

        return (T) instance;
    }

   private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

   private Class<?> getAdaptiveExtensionClass() {
        // 获取目标extensionClasses,如果无法获取到,则在定义文件中进行加载
        getExtensionClasses();
        // 如果目标类型有使用@Adaptive标注的子类型,则直接使用该子类作为装饰类
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        // 如果目标类型没有使用@Adaptive标注的子类型,则尝试在目标接口中查找是否有使用@Adaptive标注的
        // 方法,如果有,则为该方法动态生成子类装饰代码
        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);
    }

从Compiler的代码,默认实现是javassist。
JavassistCompiler会使用一个StringBuilder来构建自适应类的Java源码。
先生成Java源代码,然后编译,加载到jvm中。通过这种方式,可以更好的控制生成的Java类。而且这样也不用care各个字节码生成框架的api等。
这个的确很有意思,也很奇妙,感兴趣的可以去源码详细看一下。
有几个点需要注意的是,在生成代码的时候,AdaptiveClassCodeGenerator会对包含Adaptive注解的方法加入具体实现,否则,直接抛错UnsupportedOperationException

3.getExtensionLoader()

这个方法属于核心方法,但是内容相对简单,主要作用是为当前接口类型实例化一个ExtensionLoader对象,然后将其缓存起来

相关文章

  • Dubbo SPI原理详解

    SPI全称为Service Provider Interface,是一种服务提供机制,相对于接口调用而言,可以让调...

  • 1.0 dubbo源码解析之@SPI

    @SPI是了解dubbo源码的基础,dubbo-spi是通过jdk-spi优化而来的,对于jdk-spi的原理这里...

  • dubbo之ExtensionLoader调用

    参考 Dubbo实现原理之基于SPI思想实现Dubbo内核

  • dubbo原理:SPI机制(二)

    在上一篇:SPI机制(一)中研究了Dubbo SPI的自适应原理;SPI机制(二)中我们来研究下Dubbo SPI...

  • Dubbo第三天

    5. SPI 机制原理 因为dubbo 框架是建立的 SPI 机制上,因此在探寻 dubbo 框架源码前,我们需要...

  • Dubbo之SPI实现原理详解

    开篇  SPI全称为Service Provider Interface,是一种服务提供机制,比如在现实中我们经常...

  • 详解Dubbo(十):SPI实现原理

    前言 前面用了十几篇文章讲了Dubbo的基本原理和代码实现,基本的调用过程覆盖的差不多了。后续文章讲讲在面试中经常...

  • dubbo源码(三)-Adaptive使用

    前言 前面两篇文章对dubbo SPI的使用和原理进行简单的讲解,大家应该对dubbo SPI有了认识。在 Dub...

  • dubbo过滤器

    加载原理 dubbo的过滤器整体都是采用SPI的方式进行加载的 首先通过SPI加载dubbo加载策略 默认有三种策...

  • Dubbo SPI 源码学习 & admin安装(二)

    笔记简述本学习笔记主要是介绍了SPI的使用以及原理,dubbo是如何实现自身的SPI,dubbo如何使用的可以看D...

网友评论

      本文标题:Dubbo SPI原理详解

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