【Dubbo源码】SPI机制源码解析

简介: 【Dubbo源码】SPI机制源码解析

文章目录

什么是SPI机制

SPI示例

Java SPI示例

Dubbo SPI 示例

Dubbo SPI机制源码分析

ExtensionLoader静态类和静态方法

new ExtensionLoader(type) 实例化扩展加载器

如何创建自适应扩展实例

如何获取自适应扩展的`Class`?

如何自动创建`自适应扩展类`

实例化之后,如何依赖注入?

依赖注入的属性从哪里来

加载当前`Type`中所有的扩展类

加载文件中的具体实现类

loader.getExtension(name)根据名称获取扩展类实例

@Activate 注解的作用

总结

自适应扩展机制

扩展点自动包装

扩展点自动装配

扩展点自动激活

参考文章

什么是SPI机制

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法,然后再来分析 Dubbo SPI 的源码。


SPI示例

Java SPI示例

前面简单介绍了 SPI 机制的原理,本节通过一个示例演示 Java SPI 的使用方法。首先,我们定义一个接口,名称为 Robot。

public interface Robot {
    void sayHello();
}

接下来定义两个实现类,分别为 OptimusPrime 和 Bumblebee。

public class OptimusPrime implements Robot {
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Optimus Prime.");
    }
}
public class Bumblebee implements Robot {
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee.");
    }
}

接下来 META-INF/services 文件夹下创建一个文件,名称为 Robot 的全限定名 org.apache.spi.Robot。文件内容为实现类的全限定的类名,如下:

org.apache.spi.OptimusPrime
org.apache.spi.Bumblebee

做好所需的准备工作,接下来编写代码进行测试。

public class JavaSPITest {
    @Test
    public void sayHello() throws Exception {
        ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
        System.out.println("Java SPI");
        serviceLoader.forEach(Robot::sayHello);
    }
}

image.png

从测试结果可以看出,我们的两个实现类被成功的加载,并输出了相应的内容。关于 Java SPI 的演示先到这里,接下来演示 Dubbo SPI。


Dubbo SPI 示例

Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下。

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。下面来演示 Dubbo SPI 的用法:

public class DubboSPITest {
    @Test
    public void sayHello() throws Exception {
        ExtensionLoader<Robot> extensionLoader = 
            ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        bumblebee.sayHello();
    }
}

image.png

Dubbo SPI机制源码分析

SPI机制的实现类是ExtensionLoader<T>;所以主要本篇文章主要分析这个类的源码;


ExtensionLoader静态类和静态方法

如果你有留心dubbo使用SPI机制的时候,无非大部分都是通过一个static静态方法来调用的,而且有很多的静态属性来保存全局的SPI实例;我们先了解一下这些静态方法和属性


静态属性

  //文件路径-> (以接口类名为文件名,文件内容为实现类) 一般这个里面存放自定义服务相关类
    private static final String SERVICES_DIRECTORY = "META-INF/services/";
    //文件路径-> (以接口类名为文件名,文件内容为实现类) 一般这个里面存放dubbo相关的类
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
    //文件路径-> (以接口类名为文件名,文件内容为实现类) 这个存放的就是dubbo框架自身的类
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
    //分隔符
    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
   //存放所有需要扩展的接口类名,和对应的ExtensionLoader扩展加载器
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();

前面三个文件夹路径SERVICES_DIRECTORY、DUBBO_DIRECTORY、DUBBO_INTERNAL_DIRECTORY本质上没有区别,因为这三个路径都会被dubbo扫描一遍,把这些文件夹下面的文件全部加载到内存中;


ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS这个Map是用来存放所有的SPI的管理的Class和ExtensionLoader扩展器的;

比如dubbo内置的


image.png

image.png

上面的这些文件名都是一个interface的全类名路径;那么我们的EXTENSION_LOADERS中的key就对应这些interface,value就对应一个单独的ExtensionLoader扩展器;


ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES

TODO…


静态方法

    //是否有SPI注解
    private static <T> boolean withExtensionAnnotation(Class<T> type) {
        return type.isAnnotationPresent(SPI.class);
    }
    @SuppressWarnings("unchecked")
    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!");
        }
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }
        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;
    }
  //获取ClassLoader
    private static ClassLoader findClassLoader() {
        return ExtensionLoader.class.getClassLoader();
    }

主要看上面的getExtensionLoader方法,这个是非常关键的一个方法,因为dubbo想要获取对应Class的一个实例,那么需要先获取这个Class的ExtensionLoader扩展加载器,这个方法就是对外提供的一个入口;

这个Class必须是一个interface,必须打上了SPI的注解

我们发现获取这个 ExtensionLoader扩展加载器 是从全局静态变量EXTENSION_LOADERS获取的;但是一开始没有的情况,需要先实例化一个 ExtensionLoader扩展加载器 出来 new ExtensionLoader<T>(type) ;

为了方便分析源码,我们一起启动服务,开启Debug;Dubbo最先调用ExtensionLoader的是

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


image.png

image.png

new ExtensionLoader(type) 实例化扩展加载器

以首先被加载的Protocol为例

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

最终是执行了new ExtensionLoader<T>(type)的方法;并且保存到了静态属性EXTENSION_LOADERS中;

我们看看是怎么实例化的

    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

当前的type=interface com.alibaba.dubbo.rpc.Protocol; 那么这个ExtensionFactory objectFactory;属性又是什么呢?

TODO…

那么这里要先去执行

ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());

这个时候的type=interface com.alibaba.dubbo.common.extension.ExtensionFactory


当ExtensionFactory也new了一个ExtensionLoader之后,然后去调用方法getAdaptiveExtension(); 这个方法的作用是

获取自适应扩展 ;

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("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            } else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }
        return (T) instance;
    }

cachedAdaptiveInstance缓存了自适应扩展的实例类;

createAdaptiveExtension()方法创建了自适应扩展的实例,并存放入cachedAdaptiveInstance

如果这个自适应扩展实例存在的话就直接返回了

那么,是如何创建 自适应扩展实例的呢?

如何创建自适应扩展实例

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

简而言之


先获取自适应扩展的Class

然后调用Class的newInstance()方法来实例化对象

生成的对象,可能还有一些属性要注入,所以执行了方法injectExtension依赖注入;

那么问题又来了

如何获取自适应扩展的Class?

实例化之后,如何依赖注入?


如何获取自适应扩展的Class?

获取自适应扩展的Class, 得先加载文件夹下面的文件啊,自适应扩展也是SPI机制中管理的其中一个比较特殊的类而已;

  private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

加载当前type中所有的扩展类,(加载的具体详情请看下面)

如果扩展类中有带有注解@Adaptive,说明是自适应扩展类,直接返回

一个type有且只有一个自适应扩展类

如果当前type中所有的扩展类中没有找到带有注解@Adaptive自适应扩展类的话,就会主动去创建一个自适应扩展类

如何自动创建自适应扩展类

    private Class<?> createAdaptiveExtensionClass() {
        String code = createAdaptiveExtensionClassCode();
        ClassLoader classLoader = findClassLoader();
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

如果没有自适应扩展类,则dubbo会自动生成一个;

先拼接类


查询当前type的所有方法中是否有注解@Adaptive(是方法中的注解),如果一个都没有的话,那么就会抛出异常;

遍历每一个method,如果方法中没有注解,则该方法拼接出来的就是直接抛异常说不支持该方法;

拼接的过程太繁琐了,直接给一个拼接之后的例子吧

以Protocol为例子

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }
    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }
    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1)
            throws com.alibaba.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) " +
                    "name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)
                ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol)" +
                " name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)
                ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
}

拼接这个类最主要的地方在ExtensionLoader.getExtensionLoader(T).getExtension(extName);; 这个extName究竟是多少,拼接的逻辑在下面,那个getNameCode就是最终的extName

image.png

拼接完成这个类之后,然后选择某个Compiler来生产字节码;选择Compiler也是通过SPI选择的;

实例化之后,如何依赖注入?

上面已经分析完如何获取自适应扩展类; 实例完了之后还没有完成;因为扩展类 里面可能还有设置一些属性;所有还有一个依赖注入的过程

 private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && 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==null的情况就直接返回了,不需要依赖注入;什么情况下这个值是null?,只有type=com.alibaba.dubbo.common.extension.ExtensionFactory;情况下这个才直接返回了;

如果实例中的方法是 set 开头的并且只有一个入参,并且是public权限的,就可以依赖注入了

那么注入的属性从哪里来呢?

依赖注入的属性从哪里来

我们可以看到是从Object object = objectFactory.getExtension(pt, property);得到的注入属性,然后执行method.invoke(instance, object);进行注入;

从扩展工厂类中获取 扩展实例

这个objectFactory=ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension();

dubbo中的自适应扩展类是AdaptiveExtensionFactory

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
    private final List<ExtensionFactory> factories;
    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }
    @Override
    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }
}

loader.getSupportedExtensions()获取到的是除了自适应类、包装类之外的扩展类;那么这个方法得到的名字有两个,①SpiExtensionFactory ②SpringExtensionFactory, 拿到的只是名字而已,那么还要通过loader.getExtension(name)来拿到对应的实例对象! 具体的创建实例对象细节看后面

在这里的情况,那么factories中就有两个扩展实例

1.SpiExtensionFactory SPI的扩展工厂

2.SpringExtensionFactory Spirng的扩展工厂

那么获取属性的时候我们看到调用了Object object = objectFactory.getExtension(pt, property); 执行的方法就是


image.png

image.png

可以看到遍历执行SpiExtensionFactorySpringExtensionFactory两个扩展类的getExtension方法;

例如SpiExtensionFactory;

image.png

所以这个扩展工厂类,我们也可以写自己的扩展工厂类来生成对应的对象来依赖注入对象!

加载当前Type中所有的扩展类

加载扩展类比较重要,所以我单独拉出来细说

    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;
    }
    // synchronized in getExtensionClasses
    private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if ((value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                if (names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                if (names.length == 1) cachedDefaultName = names[0];
            }
        }
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadDirectory(extensionClasses, DUBBO_DIRECTORY);
        loadDirectory(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

如果cachedClasses不为空直接返回;说明已经加载过了,这个就是用来保存当前Class中的所有扩展类名;

image.png

cachedClasses的key是左边值,value是右边对应的Class

如果还没有加载过,则开始加载

如果当前的type上的@SPI有默认值,例如@SPI("dubbo"),则将其设置到属性cachedDefaultName中;

加载三个文件夹DUBBO_INTERNAL_DIRECTORY、DUBBO_DIRECTORY、SERVICES_DIRECTORY下面的对应type文件中的具体实现类;

加载文件中的具体实现类

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 when load extension class(interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + "is not subtype of interface.");
        }
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            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());
            }
        } else if (isWrapperClass(clazz)) {
            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
            }
            wrappers.add(clazz);
        } else {
            clazz.getConstructor();
            if (name == null || name.length() == 0) {
                name = findAnnotationName(clazz);
                if (name == null || name.length() == 0) {
                    if (clazz.getSimpleName().length() > type.getSimpleName().length()
                            && clazz.getSimpleName().endsWith(type.getSimpleName())) {
                        name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
                    } else {
                        throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                    }
                }
            }
            String[] names = NAME_SEPARATOR.split(name);
            if (names != null && names.length > 0) {
                Activate activate = clazz.getAnnotation(Activate.class);
                if (activate != null) {
                    cachedActivates.put(names[0], activate);
                }
                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("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                    }
                }
            }
        }
    }

文件中的扩展类必须是当前type的实现类

如果扩展类中带有注解@Adaptive则表示这个是自适应扩展类;并且缓存到属性cachedAdaptiveClass中;如果文件中的扩展类有多个@Adaptive,则会抛异常,最多只有一个自适应扩展类

如果当前加载的扩展类是一个包装类(如果这个扩展类有一个构造函数并且入参是当前type的情况),则将这个包装类加入的一个Map属性cachedWrapperClasses中; 这个属性保存了所有的包装类;

如果不是自适应扩展类也不是包装类,则将此扩展类放入map属性cachedNames中;key是扩展类的Class,value是name;这个就是用来维护一个扩展类有几个名字的;因为这个左边的name可以用逗号来分割;


image.png

image.png

如果不是自适应扩展类也不是包装类,并且扩展类带有注解@Activate,则放入map属性cachedActivates中;key是name, value是注解Activate

loader.getExtension(name)根据名称获取扩展类实例

前面讲了 自适应扩展类的实例化,还有将各个Class加载到内存中;但是这个时候其他的扩展类还没有实例化的;

那么 在加载完扩展类之后,具体是如何将这些扩展类实例化的呢?


可以看到,dubbo只是加载这些扩展类而已,这个时候并没有去加载这里类并且实例化;只有在需要这些扩展类实例的时候,才会去主动实例化;

  public T getExtension(String name) {
        if (name == null || name.length() == 0)
            throw new IllegalArgumentException("Extension name == null");
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<Object>());
            holder = cachedInstances.get(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<?> 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("Extension instance(name: " + name + ", class: " +
                    type + ")  could not be instantiated: " + t.getMessage(), t);
        }
    }

如果name=true,则实例化默认的扩展类;就是之前加载过程中赋值 cachedDefaultName 的扩展类,也就是@SPI("默认")里面的值

开始创建实例createExtension,通过name找到对应的Class,然后调用clazz.newInstance()进行实例化;将实例化对象存到静态全局变量EXTENSION_INSTANCES中;

调用injectExtension进行依赖注入;上面分析过,这里不做过多分析

实例化所有的包装类,instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); ,可以看到遍历了所有的包装类,并且每实例化成功一个包装类并且镜像依赖注入的操作之后,这个新的实例就成为下一个包装类实例化时候的入参; 反正就是可以一层一层的往下包装下去;

将创建好的实例放到cachedInstances中

@Activate 注解的作用

TODO…


总结

自适应扩展机制

在 Dubbo 中,很多拓展都是通过 SPI 机制进行加载的,比如 Protocol、Cluster、LoadBalance 等。有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。这听起来有些矛盾。拓展未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,拓展就无法被加载。对于这个矛盾的问题,Dubbo 通过自适应拓展机制很好的解决了。自适应拓展机制的实现逻辑比较复杂,首先 Dubbo 会为拓展接口生成具有代理功能的代码。然后通过 javassist 或 jdk 编译这段代码,得到 Class 类。最后再通过反射创建代理类,整个过程比较复杂

Adaptive 可注解在类或方法上。当 Adaptive 注解在类上时,Dubbo 不会为该类生成代理类。注解在方法(接口方法)上时,Dubbo 则会为该方法生成代理逻辑。Adaptive 注解在类上的情况很少,在 Dubbo 中,仅有两个类被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。此种情况,表示拓展的加载逻辑由人工编码完成。更多时候,Adaptive 是注解在接口方法上的,表示拓展的加载逻辑需由框架自动生成。Adaptive 注解的地方不同,相应的处理逻辑也是不同的。

对于要生成自适应拓展的接口,Dubbo 要求该接口至少有一个方法被 Adaptive 注解修饰。若不满足此条件,就会抛出运行时异常

一个方法可以被 Adaptive 注解修饰,也可以不被修饰。这里将未被 Adaptive 注解修饰的方法称为“无 Adaptive 注解方法,生成的方法是抛出异常throw new UnsupportedOperationException

方法代理逻辑会从 URL 中提取目标拓展的名称,Dubbo 使用 URL 对象(包含了Key-Value)传递配置信息。

扩展点方法调用会有URL参数(或是参数有URL成员)

这样依赖的扩展点也可以从URL拿到配置信息,所有的扩展点自己定好配置的Key后,配置信息从URL上从最外层传入。URL在配置传递上即是一条总线

Adaptive 注解值 value 类型为 String[],可填写多个值,默认情况下为空数组。若 value 为非空数组,直接获取数组内容即可。若 value 为空数组,则需进行额外处理。处理过程是将类名转换为字符数组,然后遍历字符数组,并将字符放入 StringBuilder 中。若字符为大写字母,则向 StringBuilder 中添加点号,随后将字符变为小写存入 StringBuilder 中。比如 LoadBalance 经过处理后,得到load.balance。

上面得到的值,例如load.balance, 然后从URL中取获取对应需要自适应扩展的实现类名;用url.getParameter(value,defaultvalue)得到extName需要要使用的扩展类;

最后(com.alibaba.dubbo.rpc.Protocol) ExtensionLoader .getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName) 拿到了正真要使用的扩展类,然后用这个实例类去调用 被@Adaptive修饰的方法名;

例如return extension.refer(arg0, arg1);

扩展点自动包装

自动包装扩展点的 Wrapper 类。ExtensionLoader 在加载扩展点时,如果加载到的扩展点有拷贝构造函数,则判定为扩展点 Wrapper 类。

Wrapper 类同样实现了扩展点接口,但是 Wrapper 不是扩展点的真正实现。它的用途主要是用于从 ExtensionLoader 返回扩展点时,包装在真正的扩展点实现外。即从 ExtensionLoader 中返回的实际上是 Wrapper 类的实例,Wrapper 持有了实际的扩展点实现类

扩展点的 Wrapper 类可以有多个,也可以根据需要新增。

通过 Wrapper 类可以把所有扩展点公共逻辑移至 Wrapper 中。新加的 Wrapper 在所有的扩展点上添加了逻辑,有些类似 AOP,即 Wrapper 代理了扩展点。

扩展点自动装配

主要的方法就是injectExtension;

Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。

objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展。

Dubbo IOC 目前仅支持 setter 方式注入

扩展点自动激活

对于集合类扩展点,比如:Filter, InvokerListener, ExportListener, TelnetHandler, StatusChecker 等,可以同时加载多个实现,此时,可以用自动激活来简化配置,如:

import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
@Activate // 无条件自动激活
public class XxxFilter implements Filter {
    // ...
}
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
@Activate("xxx") // 当配置了xxx参数,并且参数为有效值时激活,比如配了cache="lru",自动激活CacheFilter。
public class XxxFilter implements Filter {
    // ...
}
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
@Activate(group = "provider", value = "xxx") // 只对提供方激活,group可选"provider"或"consumer"
public class XxxFilter implements Filter {
    // ...
}


相关文章
|
8月前
|
监控 Dubbo Java
由浅入深Dubbo核心源码剖析SPI机制 2
由浅入深Dubbo核心源码剖析SPI机制
34 0
|
4月前
|
缓存 Dubbo Java
Dubbo 第三节_ Dubbo的可扩展机制SPI源码解析
Dubbo会对DubboProtocol对象进⾏依赖注⼊(也就是⾃动给属性赋值,属性的类型为⼀个接⼝,记为A接⼝),这个时候,对于Dubbo来说它并不知道该给这个属性赋什么值,换句话说,Dubbo并不知道在进⾏依赖注⼊时该找⼀个什么的的扩展点对象给这个属性,这时就会预先赋值⼀个A接⼝的⾃适应扩展点实例,也就是A接⼝的⼀个代理对象。在调⽤getExtension去获取⼀个扩展点实例后,会对实例进⾏缓存,下次再获取同样名字的扩展点实例时就会从缓存中拿了。Protocol是⼀个接。但是,不是只要在⽅法上加了。
|
6月前
|
缓存 Dubbo Java
Dubbo2.7的Dubbo SPI实现原理细节
Dubbo2.7的Dubbo SPI实现原理细节
33 0
|
7月前
|
Dubbo Java 应用服务中间件
JDK SPI、Spring SPI、Dubbo SPI三种机制的细节与演化
Java SPI(Service Provider Interface)是JDK提供的一种服务发现机制,用于在运行时动态加载和扩展应用程序中的服务提供者。
175 0
|
7月前
|
存储 Dubbo Java
Dubbo第三讲:Dubbo的可扩展机制SPI源码解析
Dubbo第三讲:Dubbo的可扩展机制SPI源码解析
|
9月前
|
缓存 Dubbo Java
(一)Dubbo源码解析:增强SPI
(一)Dubbo源码解析:增强SPI
64 0
|
运维 Dubbo 中间件
Dubbo3 源码解读-宋小生-6:Dubbo的SPI扩展机制之普通扩展对象的创建与Wrapper机制的源码解析
> Dubbo3 已经全面取代 HSF2 成为阿里的下一代服务框架,2022 双十一基于 Dubbo3 首次实现了关键业务不停推、不降级的全面用户体验提升,从技术上,大幅提高研发与运维效率的同时地址推送等关键资源利用率提升超 40%,基于三位一体的开源中间件体系打造了阿里在云上的单元化最佳实践和统一标准,同时将规模化实践经验与技术创新贡献开源社区,极大的推动了开源技术与标准的发展。 > 本文是
167 0
|
缓存 开发框架 Dubbo
从源码分析Dubbo的SPI机制
从源码分析Dubbo的SPI机制
170 0
从源码分析Dubbo的SPI机制
|
存储 设计模式 缓存
Dubbo SPI 实现原理
Dubbo SPI 实现原理
121 0
Dubbo SPI 实现原理
|
XML 缓存 Dubbo
dubbo的spi机制分析和实战案例(下)
dubbo的spi机制分析和实战案例(下)
205 0