Dubbo系列讲解之扩展点实现原理分析【2万字分享】

ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);

if (loader == null) {

EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));

loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);

}

return loader;

}

该方法主要做了以下几件事

  • 对传入的的扩展点进行判断

空值判断

是否是接口判断

是否标识了@SPI注解的判断,这也印证了前面我们说的只有标识了@SPI注解的接口Dubbo才会认为它是个扩展点接口

  • 根据类型从EXTENSION_LOADERS缓存中获取ExtensionLoader,获取到就直接返回ExtensionLoader。(EXTENSION_LOADERS是一个CurrentHashMap集合,key为扩展点接口的.class对象,value为该扩展点对应的ExtensionLoader)

  • 如果缓存中未获取到的ExtensionLoader,以扩展点.class对象为key,创建一个ExtensionLoader对象为value存储到EXTENSION_LOADERS中,返回创建的ExtensionLoader。到此就可以获取到一个ExtensionLoader了,通过返回的ExtensionLoader对象可以获得对应的扩展点的实现对象

接下来进入ExtensionLoader类中的构造方法,看看ExtensionLoader实例化时做了什么

private ExtensionLoader(Class<?> type) {

this.type = type;

objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());

}

在构造器中为type属性复制传入的扩展点的.class对象。同时通过自适应扩展点的方式获取到了一个ExtensionFactory的扩展点的实现,赋值给objectFactory。这里先不详细说明会具体获取到哪个实现,本节分析完成再回过来看。

通过以上的步骤,一个初始化完成的ExtensionLoader对象已经被获取到了,分析getAdaptiveExtension()获取自适应扩展点实现的流程

进入getAdaptiveExtension()

public T getAdaptiveExtension() {

Object instance = cachedAdaptiveInstance.get();

if (instance == null) {

// 如果获取到的实例为null,切缓存的错误不能null,抛出异常

if (createAdaptiveInstanceError != null) {

throw new IllegalStateException("Failed to create adaptive instance: " +

createAdaptiveInstanceError.toString(),

createAdaptiveInstanceError);

}

synchronized (cachedAdaptiveInstance) {

instance = cachedAdaptiveInstance.get();

if (instance == null) {

try {

instance = createAdaptiveExtension();

cachedAdaptiveInstance.set(instance);

} catch (Throwable t) {

// 异常信息缓存起来,下一次进来时如果发现是创建实例是出现异常,就直接抛出异常。这里的设计应该是当扩展点创建异常时避免多次执行创建流程的优化

createAdaptiveInstanceError = t;

throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);

}

}

}

}

return (T) instance;

}

该方法主要做了以下几件事

  • 从缓存cachedAdaptiveInstance中获取扩展点实现,存在该扩展点对象,则直接返回该实例。cachedAdaptiveInstance是一个Holder对象,主要用于缓存该扩展点的实现的具体实例,因为这里只会返回一个自适应扩展点的实现(有多个实现类标注了则按文件定义顺序取最后一个),实现对于每个``ExtensionLoader`来说,自适应扩展点是单例的。

  • 如果扩展点实现不存在,调用createAdaptiveExtension()创建一个具体的实现,并将该实例set到cachedAdaptiveInstance中缓存起来。

创建扩展点实现的具体流程是在createAdaptiveExtension方法中

private Class<?> getAdaptiveExtensionClass() {

getExtensionClasses();

if (cachedAdaptiveClass != null) {

return cachedAdaptiveClass;

}

return cachedAdaptiveClass = createAdaptiveExtensionClass();

}

该方法主要做了以下几件事

  • 调用getExtensionClasses(),顾名思义,该方法主要是获取扩展点的所有实现的.class对象。

  • 如果缓存的cachedAdaptiveClass 对象不为null,直接返回。(cachedAdaptiveClass是一个class对象,用于保存该扩展点的自适应扩展点的实现,即是该扩展点的实现类中存在有将@Adaptive标注在类上的默认自适应扩展点)

  • 如果为缓存有cachedAdaptiveClass对象,则调用createAdaptiveExtensionClass创建一个cachedAdaptiveClass,并复制给cachedAdaptiveClass

接下来首先进入getExtensionClasses()方法

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;

}

该方法首先会从cachedClasses(cachedClasses也是一个holder,用于存储每个扩展点的所有扩展实现的map集合)获取该.class对象,存在则直接返回,否则调用loadExtensionClasses方法加载扩展点的classs,将加载到的classes存到cachedClasses中。

接下来进入loadExtensionClasses

private Map<String, Class<?>> loadExtensionClasses() {

cacheDefaultExtensionName();

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

for (LoadingStrategy strategy : strategies) {

// 扫描每个加载策略目录中的扩展点实现

loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());

// 对alibaba的践行兼容

loadDirectory(extensionClasses, strategy.directory(), type.getName().replace(“org.apache”, “com.alibaba”), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());

}

return extensionClasses;

}

首先调用cacheDefaultExtensionName()方法。再给接口标注@SPI的时候,可以给个默认的value值,表示指定的默认的扩展点实现,如@SPI(“dubbo”)public interface Protocol 表示默认的扩展点为扩展点实现名为dubbo的实现。而cacheDefaultExtensionName方法就是通过注解获取到该扩展点的默认扩展点name,赋值给cachedDefaultName

遍历strategies,获取到多个LoadingStrategy,通过stratery.directory()获取到需要扫描的目录,以下是Dubbo中默认的三种策略的实现

  • DubboInternalLoadingStrategy --> META-INF/dubbo/internal/

  • DubboLoadingStrategy -->META-INF/dubbo/

  • ServicesLoadingStrategy --> META-INF/services/

在Dubbo中,创建ExtensionLoader对象时,会load到所有的LoadingStrategy,这里利用的是JDK原生的SPI的方式,将LoadingStrategy的所有扩展实现都加载进来,保存到strategies中。所以如果需要扩展Dubbo中的扫描的路径,按照JDK的原生方式进行扩展即可

进入到loadDirectory方法

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,

boolean extensionLoaderClassLoaderFirst, boolean overridden, String… excludedPackages) {

String fileName = dir + type;

try {

Enumeration<java.net.URL> urls = null;

ClassLoader classLoader = findClassLoader();

// try to load from ExtensionLoader’s ClassLoader first

if (extensionLoaderClassLoaderFirst) {

ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();

if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {

urls = extensionLoaderClassLoader.getResources(fileName);

}

}

if (urls == null || !urls.hasMoreElements()) {

if (classLoader != null) {

urls = classLoader.getResources(fileName);

} else {

urls = ClassLoader.getSystemResources(fileName);

}

}

if (urls != null) {

while (urls.hasMoreElements()) {

java.net.URL resourceURL = urls.nextElement();

loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);

}

}

} catch (Throwable t) {

logger.error("Exception occurred when loading extension class (interface: " +

type + ", description file: " + fileName + “).”, t);

}

}

该方法首先扫描传入路径下的所有的以type全类名命名的文件,获取到资源,将获取到的文件转换成Resource,传入到loadResource()方法中

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,

java.net.URL resourceURL, boolean overridden, String… excludedPackages) {

try {

try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {

String line;

// 按行读取文件中的每行数据

while ((line = reader.readLine()) != null) {

final int ci = line.indexOf(‘#’);

if (ci >= 0) {

line = line.substring(0, ci);

}

line = line.trim();

if (line.length() > 0) {

try {

String name = null;

// 通过等号分割,等号前的为key(扩展点name),等号后的为类的全类名

int i = line.indexOf(‘=’);

if (i > 0) {

name = line.substring(0, i).trim();

line = line.substring(i + 1).trim();

}

if (line.length() > 0 && !isExcluded(line, excludedPackages)) {

loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);

}

} catch (Throwable t) {

IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);

exceptions.put(line, e);

}

}

}

}

} catch (Throwable t) {

logger.error("Exception occurred when loading extension class (interface: " +

type + ", class file: " + resourceURL + ") in " + resourceURL, t);

}

}

首先按行读取文件,对读取到的每一行数据通过=号进行分割,=号前为扩展点的名字,=号后的为扩展点的具体扩展的实现类的全类名

通过Class.forName将实现类加载到内存中。传入到loadClass()方法

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,

boolean overridden) throws NoSuchMethodException {

if (!type.isAssignableFrom(clazz)) {

throw new IllegalStateException("Error occurred when loading extension class (interface: " +

type + ", class line: " + clazz.getName() + "), class "

  • clazz.getName() + " is not subtype of interface.");

}

if (clazz.isAnnotationPresent(Adaptive.class)) {

// 会覆盖掉之前保存的cachedAdaptiveClass

cacheAdaptiveClass(clazz, overridden);

} else if (isWrapperClass(clazz)) {

cacheWrapperClass(clazz);

} else {

clazz.getConstructor();

if (StringUtils.isEmpty(name)) {

// 如果扩展文件中的name为空,则调用findAnnotationName方法获取扩展点名字,具体命名方式这里就不详细看了

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 (ArrayUtils.isNotEmpty(names)) {

cacheActivateClass(clazz, names[0]);

for (String n : names) {

cacheName(clazz, n);

saveInExtensionClass(extensionClasses, clazz, n, overridden);

}

}

}

}

该方法主要主要做了以下几件事

  • 判断该对象是否实现了扩展点接口,未实现则抛出异常

  • 判断给实例是否是自适应扩展点,是,调用cacheAdaptiveClass方法将该扩展点复制到cachedAdaptiveClass成员变量中。

  • 判断该实例是否是扩展点的wrapper,是则调用cachedWrapperClasses方法将该实例保存到cachedWrapperClasses中。扩展点实现是否是wrapper的判断条件为该实现类中存在一个以扩展点为入参的构造方法时。

  • 对name进行分割,获取到单个扩展点名字,检查是否是扩展点,是,则将该实例存储到cachedActivates中

  • 缓存扩展点的名字,存储到cachedNames中,以扩展点具体实现类的.class为key,扩展点name为value

  • 调用saveInExtensionClass方法,将扩展点名字及其实现的.class保存到extensionClasses()集合中。

到这里,该扩展点在项目中的所有实现将被加载完成,且已经区分出了实现中,自适应扩展点,wrapper等不同类型的实现。然后我们回到

再次回到getAdaptiveExtensionClass()方法,当执行完getExtensionClasses();方法之后,如果cacheAdaptiveClass为null,表示该扩展点没有默认的自适应扩展点,此时扩展点需要将需要自适应扩展的方法上标注@Adaptive(),并且该方法中需要传入URL对象,因为Dubbo中需要将都是通过URL来携带配置的。

将调用createAdaptiveExtensionClass()方法动态创建一个自适应扩展点

private Class<?> createAdaptiveExtensionClass() {

String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();

ClassLoader classLoader = findClassLoader();

org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();

return compiler.compile(code, classLoader);

}

该方法会动态生成一个自适应扩展点的类,然后编译通过编译器编译,加载其.class文件到内存中。返回该动态类的.class对象。

生成的动态类代码如下:

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

public class HelloService$Adaptive implements com.wangx.spring.cloud.alibaba.consumer.spi.HelloService {

public java.lang.String sayHello(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注解中指定该参数的名称,default为SPI上定义的默认扩展点实现

String extName = url.getParameter(“hello.service”,“default”);

if (extName == null)

throw new IllegalStateException(“Failed to get extension (com.wangx.spring.cloud.alibaba.consumer.spi.HelloService) name from url (” + url.toString() + “) use keys([hello.service])”);

// 根据名称获取到该扩展点类型的扩展实现

com.wangx.spring.cloud.alibaba.consumer.spi.HelloService extension = (com.wangx.spring.cloud.alibaba.consumer.spi.HelloService) ExtensionLoader.getExtensionLoader(com.wangx.spring.cloud.alibaba.consumer.spi.HelloService.class).getExtension(extName);

return extension.sayHello(arg0, arg1);

}

}

该类实现了 扩展点接口,重写了扩展点中的自适应扩展方法。该方法体现的是,在运行时通过传入的URL的信息动态的获取处理当前URL时的扩展点的实现。

到这里就可以返回各种情况下的自适应扩展点的.class对象了,接下来再次回到createAdaptiveExtension方法中,通过以上的一系列操作,我们已经获取到了自适应扩展点的.class对象,并调用反射创建一个扩展点实现的对象。然后调用injectExtension进行依赖注入

private T injectExtension(T instance) {

if (objectFactory == null) {

return instance;

}

try {

for (Method method : instance.getClass().getMethods()) {

if (!isSetter(method)) {

continue;

}

/**

  • Check {@link DisableInject} to see if we need auto injection for this property

*/

if (method.getAnnotation(DisableInject.class) != null) {

continue;

}

Class<?> pt = method.getParameterTypes()[0];

if (ReflectUtils.isPrimitives(pt)) {

continue;

}

try {

String property = getSetterProperty(method);

Object object = objectFactory.getExtension(pt, property);

if (object != null) {

method.invoke(instance, object);

}

} catch (Exception e) {

logger.error("Failed to inject via method " + method.getName()

  • " of interface " + type.getName() + ": " + e.getMessage(), e);

}

}

} catch (Exception e) {

logger.error(e.getMessage(), e);

}

return instance;

}

在injectExtension方法中,有如下几个操作:

  • 判断是否存在objectFactory,为null,则直接返回实例对象

  • 遍历该对象的所有方法,过滤掉不是setter方法及被标注了@DisableInject注解的方法,过滤表setter方法参数类型为特定类型及原生类型的方法,setter方法的参数就是需要被注入的对象。

  • 根据setter方法获取被依赖注入的属性名称,然后通过 objectFactory.getExtension(pt, property);获取到被注入对象实例,执行setter方法进行依赖注入。

在初始化ExtensionLoader对象时,objectFactory是通过如下代码获取的

objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());

通过上面的一些列分析,现在已经可以知道这是一个ExtensionFactory的自适应的扩展点,根据我们自适应扩展点的获取方式,我们可以推断出该扩展点的自适应扩展点的实现。

在ExtensionFactory的所有子类实现中,我们找到了AdaptiveExtensionFactory类,该类上标注了@Adaptive,所以可以推断出objectFacotory的指向的就是AdaptiveExtensionFactory类的对象。

下面来看看AdaptiveExtensionFactory类中的实现:

@Adaptive

public class AdaptiveExtensionFactory implements ExtensionFactory {

private final List factories;

public AdaptiveExtensionFactory() {

ExtensionLoader loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);

List list = new ArrayList();

for (String name : loader.getSupportedExtensions()) {

list.add(loader.getExtension(name));

}

factories = Collections.unmodifiableList(list);

}

@Override

public T getExtension(Class type, String name) {

for (ExtensionFactory factory : factories) {

T extension = factory.getExtension(type, name);

if (extension != null) {

return extension;

}

}

return null;

}

}

在AdaptiveExtensionFactory的构造函数中,会先获取到ExtensionFactory类型的ExtensionLoader,然后通过调用loader.getSupportedExtensions()获取到ExtensionFactory的所有扩展实现的名字。通过loader.getExtension(name)根据名称获取到所有扩展点,存储到factories中。

在injectExtension中调用getExtension()方法时,将会遍历初始化时获取到的所有的ExtensionFactory的扩展点。只要在其中的一个扩展到那点中找到该扩展对象的实例,则直接返回。传入的对象type和name,获取Dubbo环境下可能存在的扩展点。

在injectExtension方法中返回的依赖注入完成的对象,即是我们需要获取的自适应扩展点对象

3.2 根据名称获取扩展点

根据名称获取扩展点,顾名思义,就是根据扩展点的名称,获取到扩展点对应的实现。这种方式在Dubbo中也被广泛应用到,主要是可以通过URL中的参数或协议作为name,在运行时根据URl动态的获取到不同方式的实现。比如获取负载均衡器等

入口如下:

HelloService HelloService = ExtensionLoader.getExtensionLoader(HelloService.class).getExtension(“helloService”);

getExtensionLoader方法在上述中已经解释清楚了,现在直接进入到getExtension方法中

public T getExtension(String name) {

//判断传入名称是否为null

if (StringUtils.isEmpty(name)) {

throw new IllegalArgumentException(“Extension name == null”);

}

// 如果为true,获取默认的扩展点,在getDefaultExtension方法中会调用getExtensionClasses->loadExtensionClasses方法,该方法中的cacheDefaultExtensionName会将默认扩展点的name赋值到cachedDefaultName中,所以当调用getDefaultExtension()即可获得默认的扩展点实现

if (“true”.equals(name)) {

return getDefaultExtension();

}

final Holder holder = getOrCreateHolder(name);

Object instance = holder.get();

if (instance == null) {

synchronized (holder) {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Java)

最后

针对以上面试题,小编已经把面试题+答案整理好了

最新大厂必问微服务面试题汇总:SpringCloud、Boot、Dubbo

最新大厂必问微服务面试题汇总:SpringCloud、Boot、Dubbo

最新大厂必问微服务面试题汇总:SpringCloud、Boot、Dubbo

面试专题

image

除了以上面试题+答案,小编同时还整理了微服务相关的实战文档也可以分享给大家学习

image

image

image
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**[外链图片转存中…(img-gocD8yQM-1713827154207)]

[外链图片转存中…(img-AMTWBlmJ-1713827154207)]

[外链图片转存中…(img-XWi9UI8r-1713827154208)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Java)

[外链图片转存中…(img-amJgggbh-1713827154208)]

最后

针对以上面试题,小编已经把面试题+答案整理好了

[外链图片转存中…(img-oszCvWE7-1713827154209)]

[外链图片转存中…(img-aLyAGQn0-1713827154209)]

[外链图片转存中…(img-9FTzTynt-1713827154209)]

面试专题

[外链图片转存中…(img-5Pj7tbUV-1713827154209)]

除了以上面试题+答案,小编同时还整理了微服务相关的实战文档也可以分享给大家学习

[外链图片转存中…(img-RhZkBerk-1713827154209)]

[外链图片转存中…(img-3TRdrM2l-1713827154210)]

[外链图片转存中…(img-wBcomiXO-1713827154210)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值