Dubbo笔记衍生篇②:Dubbo SPI 原理

一、前言

本系列为个人Dubbo学习笔记衍生篇,是正文篇之外的衍生内容,内容来源于《深度剖析Apache Dubbo 核心技术内幕》,仅用于个人笔记记录。本文分析基于Dubbo2.7.0版本,由于个人理解的局限性,若文中不免出现错误,感谢指正。


Dubbo 为每一个功能点提供了一个SPI 扩展接口。而为了为了不加载不使用SPI 实例,Dubbo通过适配器模式和动态编译技术,加载指定的SPI 实现。


Dubbo 的扩展点机制是基于SDK 中的SPI 增强而来,解决了以下问题:

  1. JDK标准的SPI 会一次性实例化扩展点的所有实现,如果有些扩展点实现初始化很耗时,但又没用上,那么加载就很浪费资源。比如上面所说的Mysql 和Oracle 数据库驱动,当引入这两个包时,即使我们只需要使用其中一个驱动,另一个驱动实现类也会初始化。
  2. 如果扩展点加载失败,是不会友好的向用户通知具体异常,异常提示信息可能并不正确。
  3. 增加了对扩展点 Ioc 和 Aop 的支持,一个扩展点可以直接使用setter() 方法注入其他扩展点,也可以对扩展点使用Wrapper 类进行功能增强。

Dubbo的SPI实现,通过 适配器模式和动态编译来实现,Dubbo 加载 SPI 实现类,不再是通过 ServiceLoader,而是自定义了ExtensionLoader 类

二、动态编译和适配器

Dubbo 通过适配器模式 和 动态编译的方式解决了JDK 加载全部SPI 实现类的问题

1. 适配器模式

Dubbo 为每个功能点提供另一个SPI 扩展接口,Dubbo框架在使用扩展点功能时是对接口进行依赖的,而一个扩展接口对应了一系列的扩展实现类,那么如何选择使用哪一个扩展接口作为实现类?这是由适配器模式来做的。Dubbo提供的适配器和传统的适配器有一定不通,因为他是动态编译生成的。

比如Dubbo提供的扩展接口RegistryFactory ,在我们获取该接口SPI实现类是是如下操作。

public class SpiDemo {
    
    
    public static void main(String[] args) {
    
    
        // 获取zk 的服务注册中心工厂
        ExtensionLoader<RegistryFactory> extensionLoader = ExtensionLoader.getExtensionLoader(RegistryFactory.class);
        // 这里返回的实际上是 RegistryFactory 适配器
        RegistryFactory registryFactory = extensionLoader.getAdaptiveExtension();
        // 在调用getRegistry 之前 RegistryFactory SPI 实现类都没有进行实例化
        Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://localhost:2181"));

    }
}

但是此时返回的 RegistryFactory 并非是一个真正的SPI 实现类,而是一个动态编译生成的针对 RegistryFactory 接口的适配器。其代码如下:

import org.apache.dubbo.common.extension.ExtensionLoader;

public class RegistryFactory$Adaptive implements org.apache.dubbo.registry.RegistryFactory {
    
    
    @Override
    public org.apache.dubbo.registry.Registry getRegistry(org.apache.dubbo.common.URL arg0) {
    
    
        if (arg0 == null) {
    
    
            throw new IllegalArgumentException("url == null");
        }
        org.apache.dubbo.common.URL url = arg0;
        // 获取协议内容,默认为 dubbo类型,否则根据协议加载指定的实现类
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null) {
    
    
            throw new IllegalStateException("Fail to get extension(org.apache.dubbo.registry.RegistryFactory) name from url(" + url.toString() + ") use keys([protocol])");
        }
        // 根据 extName 去META-INF 目录下找到该接口的SPI文件,根据extName 为key,获取value为实现类的全路径类名。直到调用了该方法,SPI 实现类才真正实现了初始化
        org.apache.dubbo.registry.RegistryFactory extension = (org.apache.dubbo.registry.RegistryFactory) ExtensionLoader.getExtensionLoader(org.apache.dubbo.registry.RegistryFactory.class).getExtension(extName);
        // 调用真正实现类的方法
        return extension.getRegistry(arg0);
    }
}

在Dubbo中URL 是一个核心概念,Dubbo框架把所需的参数都拼接到了URL 对象里。

总结: 适配器类会根据传递的协议参数的不同,加载不同的SPI 实现类。

2. 动态编译

在进行进一步分析之前,我们先来解释一下Dubbo的动态编译:
众所周知,Java 程序想要运行首先需要将Java源代码编译成字节码文件,然后JVM把字节码文件加载到内存创建Class 对象后,使用Class对象创建实例。
正常情况下,我们是将所有源文件编译为字节码文件,然后由JVM统一加载,而动态编译则是在JVM进程运行时把源文件编译为字节码文件,然后使用字节码文件创建实例

在上面,我们讲到Dubbo会为每一个SPI 接口生成一个适配器,但这个适配器并不是写好的,而是动态编译生成的。
Dubbo提供了一个org.apache.dubbo.common.compiler.Compiler 的SPI,并提供了JavassistCompiler(默认使用)、JdkCompiler两种实现类

@SPI("javassist")
public interface Compiler {
    
    
	// code 为源码, classLoader 为指定的类加载器
    Class<?> compile(String code, ClassLoader classLoader);
}

通过 Compiler#compile 编译后,可以将源代码动态编译成Class 对象,之后便可以通过反射直接创建对象。

3. @Adaptive 注解

引用dubbo官方文档的一段话:

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


下面我们带入代码进行分析。

三、Dubbo SPI 原理

Dubbo SPI 的加载过程很简单,如下:

	  // 1. 获取 RegistryFactory 的扩展加载器,这里的ExtensionLoader类似于 JDK 标准 SPI 类中的 ServiceLoader 
      ExtensionLoader<RegistryFactory> extensionLoader = ExtensionLoader.getExtensionLoader(RegistryFactory.class);
      // 2. 通过适配器 + 动态编译获取到 RegistryFactory 
      RegistryFactory adaptiveExtension = extensionLoader.getAdaptiveExtension();

下面我们来逐步分析

1. ExtensionLoader.getExtensionLoader(RegistryFactory.class);

ExtensionLoader类似于 JDK 标准 SPI 类中的 ServiceLoader,下面的过程也很简单,为每个SPI 接口类创建一个自己的 ExtensionLoader

	// 在 Dubbo 中,每个扩展接口对应自己的ExtensionLoader,key为扩展接口的Class 对象,value为对应的ExtensionLoader
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

    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("Extension type(" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }
		// 获取 type 对应的 ExtensionLoader对象,如果没有,则创建一个。
        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;
    }

从上面的代码可以看出,第一个访问某个扩展接口时需要新建一个 ExtensionLoader 对象放到缓存中,后续从缓存中获取。

2. extensionLoader.getAdaptiveExtension()

这一步直接获取到了SPI 接口的适配器实例。适配器实例是通过动态编译产生的。下面我们来详细看一看。

    public T getAdaptiveExtension() {
    
    
        Object instance = cachedAdaptiveInstance.get();
        // DCL  检测是否存在当前接口对应的适配器
        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;
    }

...

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

可以看到,在 createAdaptiveExtension 方法中分为三步:

  1. getAdaptiveExtensionClass() : 创建并返回当前接口对应适配器的Class 对象
  2. getAdaptiveExtensionClass().newInstance() : 根据第一步 Class对象,创建出适配器的实例
  3. injectExtension((T) getAdaptiveExtensionClass().newInstance()); : 进行扩展点的相互依赖注入

其中,我们注重来看第一步和第三步

2.1 getAdaptiveExtensionClass()

getAdaptiveExtensionClass() 完成了 SPI 接口适配器的编译工作。其代码如下:

  	private Class<?> getAdaptiveExtensionClass() {
    
    
  		// 1. 获取了该扩展类接口的所有实现类的Class对象,并缓存到了cachedClasses z中
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
    
    
            return cachedAdaptiveClass;
        }
        // 2. 创建了适配器 Class 并返回
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    } 

下面我们分步详解:

2.1.1. getExtensionClasses();

该方法的作用是获取SPI 接口对应的所有实现类(包括扩展类)的Class,并进行缓存

		private Map<String, Class<?>> getExtensionClasses() {
    
    
			// DCL 确定没有缓存
	        Map<String, Class<?>> classes = cachedClasses.get();
	        if (classes == null) {
    
    
	            synchronized (cachedClasses) {
    
    
	                classes = cachedClasses.get();
	                if (classes == null) {
    
    
	                	// 进行 SPI 实现类 扫描
	                    classes = loadExtensionClasses();
	                    cachedClasses.set(classes);
	                }
	            }
	        }
	        return classes;
	    }
		...
	
		private Map<String, Class<?>> loadExtensionClasses() {
    
    
 		// 获取 @SPI 注解上的默认扩展名,@SPI 是Dubbo 提供的注解,用于表示 SPI 实现类的扩展名
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        // 如果被 @SPI 注解修饰,则进行处理,
        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));
                }
                // 默认实现类的名称放入cachedDefaultName,当没有指定使用哪个实现类时,使用该协议指定的实现类
                if (names.length == 1) {
    
    
                    cachedDefaultName = names[0];
                }
            }
        }
		
		// 在指定目录的Jar 里面查找扩展点。这里指定的目录包括 META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/。加载了 org.apache 和 com.alibaba 包下的指定SPI 接口(应该和 Dubbo 开始由 阿里开发后面 交由 apache 有关)
        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"));
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    
    }

这里解释一下:

  1. 以 RegistryFactory为例, SPI 注解为 @SPI("dubbo"),这里的 defaultAnnotation 则为 dubbo,随后通过 loadDirectory 方法到 META-INF/dubbo/internal/META-INF/dubbo/META-INF/services/ 目录下加载具体的扩展类,加载方式和Springboot自动装配类似,defaultAnnotation 为key,value为对应指定的加载类。这里就可以知道 Dubbo 增强SPI 加载SPI 文件的路径与JDK 不完全相同,包括 META-INF/dubbo/internal/META-INF/dubbo/META-INF/services/

  2. loadDirectory方法中完成了对 @Adaptive 注解的解析 和 SPI 的包装类缓存,
    loadDirectory 方法在数次跳转后会跳转到ExtensionLoader#loadClass方法中,其代码如下:

    	// 这里的name是在 SPI 文件中,key=value 中的  key
        private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
          
          
        	// 合法性校验,确定当前加载的clazz 是 type 的实现类,type是我们指定的SPI 接口,clazz 是加载的SPI文件的实现类
            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.");
            }
            // 对 Adaptive 注解的处理,如果当前类被 @Adaptive 修饰,则
            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());
                }
            } 
            // 判断当前类是否需要进行包装,这里判断的条件就是是否存在 “以 SPI 接口为入参的构造函数”
            // 这是Dubbo提供的一个扩展机制 自动包装,类似于AOP的形式,后面会详解。
            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) {
          
          
                	// 对 @Extension 注解的处理
                    name = findAnnotationName(clazz);
                    if (name.length() == 0) {
          
          
                        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 修饰,则将其缓存到 cachedActivates 中,在后续 ExtensionLoader#getActivateExtension(URL, String, String) 方法加载 SPI 实例时,满足条件的类 会被激活。
                    Activate activate = clazz.getAnnotation(Activate.class);
                    if (activate != null) {
          
          
                        cachedActivates.put(names[0], activate);
                    } else {
          
          
                        // support com.alibaba.dubbo.common.extension.Activate
                        // 对 alibaba  @Activate 注解的支持
                        com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
                        if (oldActivate != null) {
          
          
                            cachedActivates.put(names[0], oldActivate);
                        }
                    }
                    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());
                        }
                    }
                }
            }
        }
    

2.1.2. createAdaptiveExtensionClass();

这里的方法才是生成适配器的核心方法,步骤也很清楚,如下:

   private Class<?> createAdaptiveExtensionClass() {
    
    
    	// 1. 动态生成 适配器源代码
        String code = createAdaptiveExtensionClassCode();
        // 2. 寻找当前类的类加载器,使用的是 ExtensionLoader的类加载器
        ClassLoader classLoader = findClassLoader();
        // 3. 通过 适配器模式获取到编译类
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        // 4. 进行源码编译,生成 Class 文件
        return compiler.compile(code, classLoader);
    }

下面我们分步讲解:

  1. createAdaptiveExtensionClassCode(); : 生成 SPI 适配器的源码,这里生成源码的逻辑没有想象的那么负责,直接使用的字符串拼接。关于该方法,需要注意的是:

    1. 至少有一个方法被@Adaptive修饰,因为只有被@Adapter 注解修饰的方法才会被适配器增强
    2.@Adaptive修饰得方法得参数 必须满足参数中有一个是URL类型,或者有至少一个参数有一个“公共且非静态的返回URL的无参get方法”,否则无法给适配器方法注入参数类型
    3. @Adaptive注解中的value,value可以是一个数组,如果为空的话,则使用以下规则从接口的类名生成一个名称:将大写字符的类名分成几部分,并用点号“。”分开,例如: org.apache.dubbo.xxx.YyyInvokerWrapper ,其默认名称为String[] {
          
          "yyy.invoker.wrapper"} , 此名称将用于从URL搜索参数。
    	如 方法被@Adaptive("demo")修饰, 适配器会获取 URL 中属性名为 demo的值,作为协议名。
    
  2. findClassLoader() : 寻找编译使用的类加载器。这里和JDK 中不一样,JDK中由于 ServiceLoader 是 Bootstrap ClassLoader 类加载的,而用户类需要使用 AppClassLoader,所以使用了当前线程上下文的类加载器。而这里由于都是Dubbo提供的类,所以需要做这个措施,直接获取ExtensionLoader的类加载器即可。

  3. ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); 获取到 Compiler 的实现类。Compiler 也是 Dubbo提供的一个SPI 接口,并提供了JavassistCompiler(默认使用)、JdkCompiler两种实现类。

  4. compiler.compile(code, classLoader) : 在获取到源码、类加载器、编译类之后,就可以直接编译,返回SPI适配器的Class对象,后续通过反射即可获取到适配器对象。

2.2. injectExtension

该方法也是 Dubbo SPI的一个扩展点的实现:扩展点之间的依赖注入。

   ```java
    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
                            && Modifier.isPublic(method.getModifiers())) {
    
    
                        /**
                         * Check {@link DisableInject} to see if we need auto injection for this property
                         */
                         // 被DisableInject 注解修饰的属性不需要注释
                        if (method.getAnnotation(DisableInject.class) != null) {
    
    
                            continue;
                        }
                        // set 方法的参数是原始类型,不需要自动注入,包括String、Boolean、Number 等
                        Class<?> pt = method.getParameterTypes()[0];
                        if (ReflectUtils.isPrimitives(pt)) {
    
    
                            continue;
                        }
                        try {
    
    
                        	// 获取需要set 的属性名称的那个
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            // 查看该属性类是否存在扩展实现,这里的pt 是set方法的入参类型; property 是set 后面的属性
                            // 如  public void setCluster(Cluster cluster); 方法,这里的pt为org.apache.dubbo.rpc.cluster.Cluster,property 为 cluster
                            Object object = objectFactory.getExtension(pt, property);
                            // 如果存在则反射调用 sett方法设值 
                            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;
    }

至此,我们才创建出 SPI 适配器对象,当前还并没有创建出来 SPI 实现类对象。
我们先来总结一下通过 用户调用 ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension(); 来获取适配器 适配器的逻辑:

  1. 程序启动,创建当前SPI 接口的ExtensionLoader 对象(如果已有,则使用缓存)
  2. 在不存在适配器缓存的情况下,着手创建适配器对象。
  3. 创建适配器第一步 : 加载出所有SPI接口实现类的Class对象,并进行缓存。
  4. 创建适配器第二步 : 动态拼接SPI 适配器源码,随后进行动态编译,获取到 适配器的Class对象,并通过newInstance 进行反射获取适配器对象实例
  5. 创建适配器第三步 :对适配器对象进行 依赖注入(仅能通过setter方法注入)。

需要注意的是,此时 SPI 的实现类,尚未实例化,我们仅仅创建了SPI 适配器对象
因此,下面我们来看看 SPI对象的实例化过程。

3. SPI 实现类的初始化

SPI 实现类的实例化过程是在第一次调用SPI 接口方法时,适配器会进行实例化。以下以RegistryFactory的适配器 RegistryFactory$Adaptive 为例:

import org.apache.dubbo.common.extension.ExtensionLoader;

public class RegistryFactory$Adaptive implements org.apache.dubbo.registry.RegistryFactory {
    
    
    @Override
    public org.apache.dubbo.registry.Registry getRegistry(org.apache.dubbo.common.URL arg0) {
    
    
        if (arg0 == null) {
    
    
            throw new IllegalArgumentException("url == null");
        }
        org.apache.dubbo.common.URL url = arg0;
        // 获取协议内容,默认为 dubbo类型,否则根据协议加载指定的实现类
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null) {
    
    
            throw new IllegalStateException("Fail to get extension(org.apache.dubbo.registry.RegistryFactory) name from url(" + url.toString() + ") use keys([protocol])");
        }
        // 根据 extName去META-INF 目录下找到该接口的SPI文件,根据extName 为key,获取value为实现类的全路径类名。直到调用了该方法,SPI 实现类才真正实现了初始化
        
        org.apache.dubbo.registry.RegistryFactory extension = (org.apache.dubbo.registry.RegistryFactory) ExtensionLoader.getExtensionLoader(org.apache.dubbo.registry.RegistryFactory.class).getExtension(extName);
        // 调用真正实现类的方法
        return extension.getRegistry(arg0);
    }
}

比如RegistryFactory,默认其Protocol 为 “dubbo”,这里获取寻找 org.apache.dubbo.registry.RegistryFactory 文件中 key为 dubbo 的value值作为实现类,如下图,默认会加载 org.apache.dubbo.registry.dubbo.DubboRegistryFactory 为SPI 实现类。
在这里插入图片描述

3.1 getExtension(extName);

ExtensionLoader#getExtension 根据 extName(协议类型) 加载了对应的SPI 实现类,如果实现类存在包装类,则会创建器包装类。

	// org.apache.dubbo.common.extension.ExtensionLoader#getExtension
    public T getExtension(String name) {
    
    
    	// SPI 实现类命名的合法性
        if (name == null || name.length() == 0) {
    
    
            throw new IllegalArgumentException("Extension name == null");
        }
        // 如果为true,则使用默认扩展,即使用 cachedDefaultName 名称的 SPI 实现类
        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;
    }

	...
	// 这里开始创建 SPI实现类的实例对象
    private T createExtension(String name) {
    
    
    	// 根据name查找对应的扩展实现的Class对象
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
    
    
            throw findException(name);
        }
        // 如果缓存中不存在实例,则使用Class创建实例。
        try {
    
    
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
    
    
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // IOC 注入,如果当前SPI 依赖其他SPI,则会通过setter进行注入,上面已有详解。
            injectExtension(instance);
            
            // Wrapper 对扩展实现进行功能增强,这里的 cachedWrapperClasses 是 2.1.1 中我们解析了 loadDirectory 方法,其中包括对包装类的缓存
            // 如果 cachedWrapperClasses不为空,则说明存在该实现类的包装类,则对其包装类进行初始化。将SPI实现类作为构造入参注入
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
    
    
                for (Class<?> wrapperClass : wrapperClasses) {
    
    
                	// 对包装类进行 IOC 注入,同时通过SPI 实现类构造包装类。
                    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);
        }
    }

需要注意的是,这里存在一个 Wrapper 操作,因为Dubbo 的SPI 在IOC之外还扩展了扩展点的自动包装功能,下面详解。

四、扩展点的自动包装

在Spring Aop 总,我们可以使用多个切面对指定类的方法进行增强,在Dubbo 中也提供了类似的功能,在DUbbo中可以指定多个 Wrapper 类对指定的扩展点的实现类的方法进行增强。

如下,创建一个 RegistryFactory 的 包装类:

public class RegistryFactoryWrapper implements RegistryFactory {
    
    
    private RegistryFactory registryFactory;
	// 构造函数必须,Dubbo通过有无构造函数来判定是否是包装类
    public RegistryFactoryWrapper(RegistryFactory registryFactory) {
    
    
        this.registryFactory = registryFactory;
    }

    @Override
    public Registry getRegistry(URL url) {
    
    
    	// 调用过程
        System.out.println("这里是 RegistryFactory 的包装类");
        return registryFactory.getRegistry(url);
    }
}

随后我们建立 SPI 接口文件

在这里插入图片描述
在调用真正的 RegistryFactory 时会先调用 RegistryFactoryWrapper
在这里插入图片描述

需要注意的是,对于SPI Wrapper 类来说,不存在协议类型匹配一说, SPI Wrapper仅根据SPI 接口类型生效 。即使将 org.apache.dubbo.registry.RegistryFactory 文件的内容 改成了aaa=com.kingfish.main.spi.RegistryFactoryWrapper 或者 com.kingfish.main.spi.RegistryFactoryWrapper,依然会使用该包装类进行包装。


2.1.1 中我们解析了 loadDirectory 方法,其中包括对包装类的缓存 cachedWrapperClasses,此时可以发现,这里做的仅仅是缓存包装类,但是并没有解析包装类对应什么协议。
3.1 中 我们在这一块代码使用到了 cachedWrapperClasses。在这里,只要是缓存的包装类就会被加载使用,而不存在协议类型这一说。

		  // Wrapper 对扩展实现进行功能增强,这里的 cachedWrapperClasses 是 2.1.1 中我们解析了 loadDirectory 方法,其中包括对包装类的缓存
            // 如果 cachedWrapperClasses不为空,则说明存在该实现类的包装类,则对其包装类进行初始化。将SPI实现类作为构造入参注入
		 Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
    
    
                for (Class<?> wrapperClass : wrapperClasses) {
    
    
                	// 对包装类进行 IOC 注入
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }

所以整个Dubbo SPI 流程总结下来就是 :

  1. 程序启动,创建当前SPI 接口的ExtensionLoader 对象(如果已有,则使用缓存)
  2. 在不存在适配器缓存的情况下,着手创建适配器对象。
  3. 创建适配器第一步 : 加载出所有SPI接口实现类的Class对象,并进行缓存。同时缓存了所有SPI 接口包装类Class对象
  4. 创建适配器第二步 : 动态拼接SPI 适配器源码,随后进行动态编译,获取到 适配器的Class对象,并通过newInstance 进行反射获取适配器对象实例
  5. 创建适配器第三步 :对适配器对象进行 依赖注入(仅能通过setter方法注入)。
  6. 调用适配器的SPI 接口方法,在调用时 如果SPI 实现类没有初始化,则会通过ExtensionLoader#getExtension 进行初始化。
  7. 初始化过程中,会获取 包装类的缓存,如果存在包装类缓存,则会将SPI实现类作为一个包装器的一个构造入参,构造出包装器类并返回。也即是说在允许创建适配器和存在包装了的情况下,整个调用过程是 : SPI Adapter -> SPI wrapper -> SPI 实现类

五、其他

1. 加载所有扩展类

在 3.1 中的 getExtension(extName); 方法只会加载某一个扩展接口实现的Class对象,但有些情况下我们需要全部创建或者创建其中一部分(比如 Dubbo Filter 链的实现,就可能需要一次性执行多个的Filter的实现方法,就需要加载出多个Filter 实现),ProtocolFilterWrapper 类中的buildInvokerChain() 方法在建立 Filter 责任链时,需要把属于某一个group的所有Filter都放到责任链里,其通过如下方式来获取属于某一个组的Filter扩展实现类的:

	// getActivateExtension 方法,会搜索出与 key  和 group 匹配的 SPI 扩展类
   List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class)
   							.getActivateExtension(invoker.getUrl(), key, group);

比如,当服务端启动时只会加载 group 为 provider,同时协议类型指定为 url.getParameter(Constants.EXECUTES_KEY) 的 Filter

@Activate(group = Constants.PROVIDER, value = Constants.EXECUTES_KEY)
public class ExecuteLimitFilter implements Filter

当消费端启动时只会加载 group 为consumer,同时协议类型为 url.getParameter(Constants.ACTIVES_KEY) 的Filter的扩展实现类

@Activate(group = Constants.CONSUMER, value = Constants.ACTIVES_KEY)
public class ActiveLimitFilter implements Filter

1.1 @Activate注解

被 @Activate 注解修饰的类,在满足其过滤条件的情况下会自动被激活。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
    
    ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
    
    
 	// group 过滤条件
    String[] group() default {
    
    };

	// value过滤条件
    String[] value() default {
    
    };

   
    @Deprecated
    String[] before() default {
    
    };


    @Deprecated
    String[] after() default {
    
    };

  	// 排序信息
    int order() default 0;
}

1.2 源码分析

@Activate 的实现逻辑如下: 在 2.1.2 中我们知道了Dubbo在加载 SPI 所有实现类的时候会将被 @Activate 注解修饰的SPI 实现类缓存到 cachedActivates 中。而在通过 ExtensionLoader#getActivateExtension(URL, String, String)加载 SPI 实现类的时候,会判断是否满足 @Activate 注解的条件,如果满足,则会将其一并返回(这里便将 Filter 返回)。

下面我们从代码层面看一下 @Activate 的实现过程:

	// org.apache.dubbo.common.extension.ExtensionLoader#getActivateExtension(org.apache.dubbo.common.URL, java.lang.String, java.lang.String)
    public List<T> getActivateExtension(URL url, String key, String group) {
    
    
    	// 根据key 从 url 中获取 value
        String value = url.getParameter(key);
        return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group);
    }

	...

 	public List<T> getActivateExtension(URL url, String[] values, String group) {
    
    
        List<T> exts = new ArrayList<T>();
        List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
        if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
    
    
        	// 获取 SPI 接口的所有实现类
            getExtensionClasses();
            // 遍历所有被@Activate 注解修饰的的缓存,key为 SPI 文件中的key,value为 Activate 注解秀信息
            for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
    
    
                String name = entry.getKey();
                Object activate = entry.getValue();

                String[] activateGroup, activateValue;
				// 如果含有注解 @Activate ,则获取group 和value 值
                if (activate instanceof Activate) {
    
    
                    activateGroup = ((Activate) activate).group();
                    activateValue = ((Activate) activate).value();
                } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
    
    
                    activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
                    activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
                } else {
    
    
                    continue;
                }
                // 如果当前扩展类的 group和 Activate 注解上的group相同,且value值在URL中存在
                if (isMatchGroup(group, activateGroup)) {
    
    
                	// 获取该SPI 实现类,并保存到exts 中准备返回
                    T ext = getExtension(name);
                    if (!names.contains(name)
                            && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
                            && isActive(activateValue, url)) {
    
    
                        exts.add(ext);
                    }
                }
            }
            // 进行排序
            Collections.sort(exts, ActivateComparator.COMPARATOR);
        }
        List<T> usrs = new ArrayList<T>();
        for (int i = 0; i < names.size(); i++) {
    
    
            String name = names.get(i);
            if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX)
                    && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {
    
    
                if (Constants.DEFAULT_KEY.equals(name)) {
    
    
                    if (!usrs.isEmpty()) {
    
    
                        exts.addAll(0, usrs);
                        usrs.clear();
                    }
                } else {
    
    
                    T ext = getExtension(name);
                    usrs.add(ext);
                }
            }
        }
        if (!usrs.isEmpty()) {
    
    
            exts.addAll(usrs);
        }
        return exts;
    }


....

  private boolean isActive(String[] keys, URL url) {
    
    
        if (keys.length == 0) {
    
    
            return true;
        }
        // 遍历当前扩展类实现的value值
        for (String key : keys) {
    
    
        	// 遍历当前URL 存在的所有属性值
            for (Map.Entry<String, String> entry : url.getParameters().entrySet()) {
    
    
                String k = entry.getKey();
                String v = entry.getValue();
                // 如果url 中属性对的key 等于扩展接口实现的value且值相等,则返回true
                if ((k.equals(key) || k.endsWith("." + key))
                        && ConfigUtils.isNotEmpty(v)) {
    
    
                    return true;
                }
            }
        }
        return false;
    }

2. 服务提供者的自动包装

DUbbo 会给每个服务提供者的实现类创建一个Wrapper类,这个类最终调用服务提供者的接口实现类,Wrapper类的存在时为了减少反射的调用。当服务提供者收到消费者发来的请求后,根据请求方法和参数反射调用提供者的实现类,这样在每次调用时都需要进行反射,而反射本身具有性能开销,Dubbo把每个服务提供者的实现类通过JavaAssist 包装成一个Wrapper类以减少反射性能开销。而这个包装过程是在服务暴露过程(org.apache.dubbo.config.ServiceConfig#export)中发生的

	// org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory#getInvoker
    @Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
    
    
        // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
        // 生成 Wrapper类
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {
    
    
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {
    
    
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }


以上:内容部分参考
《深度剖析Apache Dubbo 核心技术内幕》
https://blog.csdn.net/weixin_33690963/article/details/94609561
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

猜你喜欢

转载自blog.csdn.net/qq_36882793/article/details/114597666