Dubbo——ExtensionLoader的工作原理

ExtensionLoader的工作原理

ExtensionLoader是整个扩展机制的主要逻辑类,在这个类里面实现了配置的加载、扩展类缓存、自适应对象生成等所有工作。

工作流程

ExtensionLoader的逻辑入口可以分为getExtension、getAdptiveExtension、getActivateExtension三个,分别是获取普通扩展类、获取自适应扩展类、获取自动激活的扩展类。总体逻辑都是从调用这三个方法开始的,每个方法可能会有不同的重载方法,根据不同的传入参数进行调整:
在这里插入图片描述
三个入口中,getActivateExtension对getExtension的依赖比较重,getAdaptiveExtension则相对独立。

getActivateExtension

getActivateExtension方法只是根据不同的条件同时激活多个普通扩展类。因此,该方法中只会做一些通用的逻辑判断,如果接口是否包含@Activate注解、匹配条件是否符合等。最终还是调用getExtesion方法获得具体扩展点实现类。

getExtension(String name)

getExtension(String name)是整个扩展加载器中最核心的方法,实现了一个完整的普通扩展类加载过程。加载过程中的每一步,都会先检查缓存中是否已经存在所需的数据,如果存在则直接从缓存中读取,没有则重新加载。这个方法每次只会根据名称返回一个扩展点实现类。

初始化过程可以分为4步:

  1. 框架读取SPI对应路径下的配置文件,并根据配置加载所有扩展类并缓存(不初始化)。
  2. 根据传入的名称初始化对应的扩展类。
  3. 尝试查找符合条件的包装类:
    3.1 包含扩展点的setter方法,例如setProtocol(Protocol protocol)方法会自动注入protcol扩展点实现;
    3.2 包含与扩展类型相同的构造函数,为其注入扩展类实例,例如本次初始化了一个ClassA,初始化完成后,会寻找构造参数中需要ClassA的包装类(Wrapper),然后注入ClassA实例,并初始化这个包装类。
  4. 返回对应的扩展类实例。
getAdaptiveExtension

getAdaptiveExtension也相对独立,只有加载配置信息部分与getExtension共用了同一个方法。和获取普通扩展类一样,框架会先检查缓存汇总是否有已经初始化好的Adaptive实例,没有则调用createAdaptiveExtension重新初始化。

初始化分为4步:

  1. 和getExtension一样先加载配置文件。
  2. 生成自适应类的代码字符串。
  3. 获取类加载器和编译器,并用编译器编译刚才生成的代码字符串。Dubbo以供有三种类型的编译器实现。
  4. 返回对应的自适应类实例。
GetExtension的实现原理

当调用getExtension(String name)方法是,会先检查缓存中是否有现成的数据没有则调用createExtension开始穿件。这里有个特殊点,如果getExtension传入的name是true,则加载并返回默认扩展类。

在调用createExtension开始创建的过程中,也会先检查缓存中是否有配置信息,如果不存在扩展类,则会从META-INF/service/、META-INF/dubbo/、META-INF/dubbo/internal/这几个路径中读取所有的配置文件,通过I/O读取字符流,然后通过解析字符串,得到配置文件中对应的扩展点实现类的全程(如com.alibaba.dubbo.common.extensionloader.activate.impl.GroupActivateeExtImpl)。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
加载完扩展点配置之后,再通过反射获得所有扩展实现类并缓存起来。注意,此处仅仅是把Class加载到JVM中,并没有做Class初始化。在加载Class文件时,会根据Class上的注解来判断扩展点类型,在根据类型分类做缓存:
在这里插入图片描述
在这里插入图片描述
最后根据传入的name找到对应的类并通过Class.forName方法进行初始化,并为其注入依赖的其他扩展类(自动加载特性)。当扩展类初始化后,会检查一次包装扩展类Set<Class<?>> wrapperClass,查找包含与扩展点类型相同的构造函数,为其注入刚初始化的扩展类:

在这里插入图片描述
在injectExtension方法中可以为类注入依赖属性,它使用了ExtensionFactory#getExtension(Class<T> type, String name)来获取对应的bean实例。

injectExtension方法总体实现了类似Spring的IOC机制,其实现原理比较简单:首先通过反射获取类的所有方法,然后遍历以字符串set开头的方法,得到set方法的参数类型,再通过ExtensionFactory寻找参数类型相同的扩展类实例,如果找到,就设值进入:

在这里插入图片描述

包装类的构造参数注入,也是通过injectExtension方法实现的。

getAdaptiveExtension的实现原理

在getAdaptiveExtension()方法中,会为扩展点接口自动生成实现类字符串,实现类主要包含以下逻辑:

为接口中每个有@Adaptive注解的方法生成默认实现(没有注解的方法则生成空实现),每个默认实现都会从URL中提取Adaptive参数值,并以此为一句动态加载扩展点。然后框架使用不同的编译器,把实现类字符串编译为自适应类并返回。

生成代码的逻辑主要分为7步:

  1. 生成package、import、类名等头部信息。此处只会引入一个类ExtensionLoader。为了不写其他类的import方法,其他方法调用时全部使用全路径。类名称会变为"接口名称 + $Adaptive"的格式。例如:Transporter接口会生成Transporter$Adaptive。
  2. 遍历接口的所有方法,获取方法的返回类型、参数类型、异常类型等。为第(3)步判断是否为空值做准备。
  3. 生成参数为空校验代码如参数是否为空的校验。如果有远程调用,还会添加Invocation参数为空的校验。
  4. 默认生成实现类名称。如果@Adaptive注解中没有设定默认值,则根据类名称生成,如YyyInvokerWrapper会被转换为yyy.invoker.wrapper。生成的规则是不断找大写字符,并把他们用"."连接起来。得到默认实现类名称后,还需要知道这个实现是哪个扩展点的。
  5. 生成获取扩展点名称的代码。根据@Adaptive注解中配置的key值生成不同的获取代码,例如:如果是@Adaptive(“protocol”),则会生成url.getProtocol()。
  6. 生成获取具体扩展实现类代码。最终还通过getExtension(extName)方法获取字是一个扩展类的真正实现。如果根据URL中配置的key没有找到对应的实现类,则会使用第(4)步中生成的默认实现类名称去找。
  7. 生成调用结果代码

演示代码生成过程:
SPI配置文件中的配置:
在这里插入图片描述
自适应接口,echo方法上有@Adaptive注解:
在这里插入图片描述
对应于"impl1"的实现类:
在这里插入图片描述
在测试方法中调动这个自适应类:
在这里插入图片描述
控制台打印:
在这里插入图片描述

会生成如下自适应代码:

package org.apache.dubbo.common.extensionloader.adaptive;
//只会导入这一个类,其它类都是以全路径方式调用
import org.apache.dubbo.common.extension.ExtensionLoader;

public class SimpleExt$Adaptive implements org.apache.dubbo.common.extensionloader.ext1.SimpleExt {
	public String echo(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
		if(arg0 == null) throw new IllegalArgumentException("url == null");
		org.apache.dubbo.common.URL url = arg0;
		/*
		 * 注意:如果@Adaptive注解没有传入key参数,则默认会把类名转化为key
		 * 如:SimpleExt会转化为 simple.ext
		 * 根据key获取对应的扩展点实现名称,第一个参数是key,第二个是获取不到时的默认值
		 * URL中没有"simple.ext"这个key,因此extName取值impl1

		 * 如果@Adaptive注解中有key参数,如@Adaptive("key1"),则会变为
		 * url.getParameter("key1", "impl1");
		 */
		String extName = url.getParameter("simple.ext", "impl");

		if(extName == null) throw new IllegalStateException(...);
		org.apache.dubbo.common.extensionloader.ext1.SimlExt extension = 
		(org.apache.dubbo.common.extsionloader.ext1.SimpleExt)ExtensionLoader
		.getExensionLoader(org.apache.dubbo.common.extesionloader.ext1.SimpleExt.class)
		.getExtension(extName);//实现类变为配置文件中的SimpleExtImpl1
		
		//最终调用真实的扩展点方法,并返回调用结果
		return extension.echo(arg0, arg1); 
	}
}

生成完代码之后就要对代码进行编译,生成一个新的Class。Dubbo中的编译器也是一个自适应接口,但@Adaptive注释是加在实现类AdaptiveCompiler上的。这样一来AdaptiveCompiler就会作为该自适应类的默认实现,不需要做代码生成和编译就可以使用了。

如果一个接口上既有@SPI(“impl1”),方法上又有@Adaptive(“impl2”)注解,那么最终动态生成的实现方法会是url.getParameter("impl2", "impl1");,即优先通过@Adaptive注解传入的key去查找扩展实现类;如果没找到,则通过@SPI注解中的key去查找;如果@SPI注解中没有默认值,则把类名转换为key,再去查找。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

getActivateExtension的实现原理

getActivateExtension(URL url, String key, String group)方法可以获取所有自动激活扩展点。参数分别是URL、URL中指定的key(多个则使用逗号隔开)和URL中指定的组信息(group)。其实现逻辑非常简单,当调用该方法时,主线流程分为4步:

  1. 检查缓存,如果缓存中没有,则初始化所有扩展实现的集合。
  2. 遍历整个@Activate注解集合,根据传入URL匹配条件(匹配group、name等),得到所有符合激活条件的扩展类实现。然后根据@Activate中配置的before、after、order等参数进行排序。
  3. 遍历所有用户自定义扩展类名称,根据用户URL配置的顺序,调整扩展点激活顺序(遵循用户在URL中配置的顺序,例如URL为test://localhost/test?ext=order1,default,则扩展点ext的激活顺序会遵循先order1再default,其中default代表所有由@Activate注解的扩展点。)
  4. 返回所有自动激活类集合。

获取Activate扩展类实现,也是通过getExtension得到的。因此,可以认为getExtension是其他两种Extension的基石。

此处有一点需要注意,如果URL的参数中传入了-default,则所有的默认@Activate都不会被激活,只有URL参数中指定的扩展点会被激活。如果传入了"-"斧蛤开头的扩展点名,则该扩展点也不会被自动激活。例如:-xxxx,表示名字为xxxx的扩展点不会被激活。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值