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

2021-07-14 10:21:34 浏览数 (1)

文章目录

  • 什么是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。

代码语言:javascript复制
public interface Robot {
    void sayHello();
}

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

代码语言:javascript复制
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。文件内容为实现类的全限定的类名,如下:

代码语言:javascript复制
org.apache.spi.OptimusPrime
org.apache.spi.Bumblebee

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

代码语言:javascript复制
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);
    }
}

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

Dubbo SPI 示例

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

代码语言:javascript复制
optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

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

代码语言:javascript复制
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();
    }
}

Dubbo SPI机制源码分析

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

ExtensionLoader静态类和静态方法

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

静态属性

代码语言:javascript复制
	//文件路径-> (以接口类名为文件名,文件内容为实现类) 一般这个里面存放自定义服务相关类
    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_DIRECTORYDUBBO_DIRECTORYDUBBO_INTERNAL_DIRECTORY本质上没有区别,因为这三个路径都会被dubbo扫描一遍,把这些文件夹下面的文件全部加载到内存中;
  • ConcurrentMap, ExtensionLoader> EXTENSION_LOADERS这个Map是用来存放所有的SPI的管理的ClassExtensionLoader扩展器的; 比如dubbo内置的

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

  • ConcurrentMap, Object> EXTENSION_INSTANCES TODO…

静态方法

代码语言:javascript复制
    //是否有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的一个实例,那么需要先获取这个ClassExtensionLoader扩展加载器,这个方法就是对外提供的一个入口;
  • 这个Class必须是一个interface,必须打上了SPI的注解
  • 我们发现获取这个 ExtensionLoader扩展加载器 是从全局静态变量EXTENSION_LOADERS获取的;但是一开始没有的情况,需要先实例化一个 ExtensionLoader扩展加载器 出来 new ExtensionLoader(type) ;

为了方便分析源码,我们一起启动服务,开启Debug;Dubbo最先调用ExtensionLoader的是 private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

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

以首先被加载的Protocol为例

代码语言:javascript复制
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

最终是执行了new ExtensionLoader(type)的方法;并且保存到了静态属性EXTENSION_LOADERS中; 我们看看是怎么实例化的

代码语言:javascript复制
    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(); 这个方法的作用是 获取自适应扩展

代码语言:javascript复制
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
  • 如果这个自适应扩展实例存在的话就直接返回了 那么,是如何创建 自适应扩展实例的呢?

如何创建自适应扩展实例

代码语言:javascript复制
    @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
  • 然后调用ClassnewInstance()方法来实例化对象
  • 生成的对象,可能还有一些属性要注入,所以执行了方法injectExtension依赖注入;

那么问题又来了 如何获取自适应扩展的Class 实例化之后,如何依赖注入?

如何获取自适应扩展的Class

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

代码语言:javascript复制
  private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
  • 加载当前type中所有的扩展类,(加载的具体详情请看下面) 如果扩展类中有带有注解@Adaptive,说明是自适应扩展类,直接返回 一个type有且只有一个自适应扩展类
  • 如果当前type中所有的扩展类中没有找到带有注解@Adaptive自适应扩展类的话,就会主动去创建一个自适应扩展类
如何自动创建自适应扩展类
代码语言:javascript复制
    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为例子
代码语言:javascript复制
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
  • 拼接完成这个类之后,然后选择某个Compiler来生产字节码;选择Compiler也是通过SPI选择的;
实例化之后,如何依赖注入?

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

代码语言:javascript复制
 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

代码语言:javascript复制
@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); 执行的方法就是

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

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

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

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

代码语言:javascript复制
    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中的所有扩展类名;

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

  • 如果还没有加载过,则开始加载
  • 如果当前的type上的@SPI有默认值,例如@SPI("dubbo"),则将其设置到属性cachedDefaultName中;
  • 加载三个文件夹DUBBO_INTERNAL_DIRECTORYDUBBO_DIRECTORYSERVICES_DIRECTORY下面的对应type文件中的具体实现类;
加载文件中的具体实现类
代码语言:javascript复制
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可以用逗号来分割;
  • 如果不是自适应扩展类也不是包装类,并且扩展类带有注解@Activate,则放入map属性cachedActivates中;key是name, value是注解Activate

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

前面讲了 自适应扩展类的实例化,还有将各个Class加载到内存中;但是这个时候其他的扩展类还没有实例化的; 那么 在加载完扩展类之后,具体是如何将这些扩展类实例化的呢?

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

代码语言:javascript复制
  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 等,可以同时加载多个实现,此时,可以用自动激活来简化配置,如:
代码语言:javascript复制
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
 
@Activate // 无条件自动激活
public class XxxFilter implements Filter {
    // ...
}
代码语言:javascript复制
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
 
@Activate("xxx") // 当配置了xxx参数,并且参数为有效值时激活,比如配了cache="lru",自动激活CacheFilter。
public class XxxFilter implements Filter {
    // ...
}
代码语言:javascript复制
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 {
    // ...
}

参考文章

DUBBO SPI 自适应扩展机制 扩展点

0 人点赞