面试常问的dubbo的spi机制到底是什么?(下)

2022-07-27 13:42:29 浏览数 (1)

本文是接着 面试常问的dubbo的spi机制到底是什么?(上)来继续剖析dubbo spi机制源码,来看看dubbo的spi机制的其它特性功能。

前文回顾

一、实现类对象构造

二、自适应机制

三、自动激活

总结

前文回顾

前一篇文章主要是讲了什么是spi机制,spi机制在java、spring中的不同实现的分析,同时也剖析了一下dubbo spi机制的实现ExtensionLoader的实现中关于实现类加载以及实现类分类的源码。

一、实现类对象构造

看实现类对象构造过程之前,先看获取,因为获取不到才构造,也就是java中spi没有的功能,按需加载。 获取实现类对象的方法是getExtension方法,传入的name参数就是短名称,也就是spi文件的键,wrap是是否包装的意思,true的意思就是对你获取的目标对象进行包装(具体什么是包装,如何包装后面会讲),wrap默认是true

接下来我们就着重分析getExtension方法

前面两个if我说一下,

第一个if比较简单,就是简单的参数校验,name参数不能为空 第二个if判断name是不是字符串true,是的话就调用getDefaultExtension,getDefaultExtension这个方法通过名称也能看出来就是获取接口默认的实现,什么是默认实现?在 面试常问的dubbo的spi机制到底是什么?(上)一文中在实现类加载的时候我提到过,默认的实现就是@SPI注解中的名称对应的实现类。

前面两个if之后就是真正获取实现了。在获取之前,先根据你是否包装构建缓存的键值,如果没有包装,就会在短名称后加上 _origin ,这主要是为了区分包不包装,然后进入getOrCreateHolder方法

里面其实就是通过缓存名称从cachedInstances获取一个Holder,获取不到就new一个Holder然后放到cachedInstances中,然后返回。Holder其实本身并没有什么意义,可以理解为一个空壳,里面放的才是真正最终返回的对象。

第一次,不用说Holder肯定没有,那么这个Holder肯定是刚new出来的。

跳出getOrCreateHolder方法,继续往下看。

从Holder中获取实现类,此时肯定是null,接下来就是synchronized,然后又是非空判断。这里其实是典型的单例模式中的双重检查机制,保证并发安全。其实从这里可以看出Holder的作用。这里是为了减少锁冲突的,因为一个实现类对象对应一个Holder对象,这样不同的实现类在创建的时候,由于Holder的不同,synchronized就不是同一个锁对象,这就起到了并发时候减少锁冲突的作用,从这可以看出dubbo设计的时候的细节是很到位的。

第一次都是null,接下来进入createExtension方法,构建对象的过程

先从实现类的缓存中获取到短名称对应的实现类,面试常问的dubbo的spi机制到底是什么?(上)一文中说到,实现类加载之后会放到内部的一个缓存中。

这个if条件判断一般肯定是false的,但是有些情况,就比如第一次构建对象抛出异常,此时第二次来构建这个对象,那么不用说肯定也会有问题,dubbo为了快速知道哪些实现类对象构造的时候会出异常,就在第一次构建对象抛异常的时候缓存了实现类的短名称到unacceptableExceptions中,当第二次来构建的时候,能够快速知道,抛出异常,减少资源的浪费。 接下来就会从extensionInstances中获取实例,这个实例是没有包装的实例,也就是说如果你获取的不带包装的实例,就是这个实例。我们看看这个实例是怎么构建出来的,这里我根据构建的不同阶段进行划分为以下几个步骤。

第一步:实例化对象

通过实例化策略InstantiationStrategy进行实例化,默认是通过无参构造器构造的。

第二步 :初始化前ExtensionPostProcessor 回调

调用 ExtensionPostProcessor的postProcessBeforeInitialization方法,ExtensionPostProcessor跟spring中的BeanPostProcessor有点像,就是对目标对象进行扩展的作用。

第三步 :依赖注入

接下来调用injectExtension方法,这个方法就是依赖注入的实现方法。

依赖注入:说白了就是dubbo会自动调用需要依赖注入的方法,传入相应的参数

哪些方法是需要依赖注入的方法?

dubbo约定 方法名以set开头,方法的参数只有一个,方法上没有加@DisableInject注解 ,方法是public的,符合这类的方法就是需要依赖注入的方法,dubbo在构建对象的时候会自动调用这些方法,传入相应的参数。

接下来进入源码

可以看出,先通过反射获取到所有的方法,然后遍历每个方法,进入两个if判断,这个判断就是判断是不是需要依赖注入的方法,也就是上面说的条件就在这个体现。

假设是需要依赖注入的方法,接下来看看如何获得需要被注入的对象,也就是方法的参数。

首先获取需要set的对象的class类型,就是方法的参数类型

然后通过getSetterProperty方法获取属性名,可以理解为bean的名称,

getSetterProperty就是方法去掉set然后第一个字母小写之后就是属性的名称,举个例子方法叫setUser,那么属性名就叫user,如果叫setUserName,属性名就叫userName,就这么简单。

最后就是根据属性名和参数类型通过 ExtensionInjector 获取需要被注入的对象。

ExtensionInjector 接口讲解

ExtensionInjector就是注入器,通过这个可以获取到被依赖注入的对象,这是个接口,有很多实现,这里是 AdaptiveExtensionInjector 实现类,也是通过spi机制获取的,ExtensionLoader构造的时候获取的。

下面列举了ExtensionInjector有的实现:

AdaptiveExtensionInjector:自适应的,本身没有实际的意义,就是遍历所有其它的ExtensionInjector实现来获取,一旦有一个获取到,就不会再调用下一个ExtensionInjector来获取的

SpiExtensionInjector:顾名思义,就是通过spi机制来获取,获取的是自适应的实现

SpringExtensionInjector:这个是通过spring容器获取实现,所以你通过dubbo的spi机制可以注入spring的bean

ScopeBeanExtensionInjector:通过dubbo内部的组件BeanFactory来获取的,BeanFactory是dubbo内部用来在一定范围的bean的容器,主要是为了对象的重复利用来的。

假设这里获取到了对象,那么接下来就是通过反射调用set方法,进行依赖注入,然后依赖注入就完成了。

第四步:ExtensionAccessorAware接口回调

如果你的接口实现了ExtensionAccessorAware接口,那么会注入给你的bean一个 ExtensionDirector ,ExtensionDirector 可以想象成是ExtensionLoader工厂,可以获取每个接口的ExtensionLoader。

第五步: 初始化后ExtensionPostProcessor回调

调用ExtensionPostProcessor的postProcessAfterInitialization方法对目标对象进行扩展的作用。

第六步:自动包装

到这一步实现类本身的对象就算构造好了,接下来就是进行自动包装,如果wrap是true的话。

自动包装:可以说是静态代理模式,就是对你的目标对象进行代理,怎么代理,就是通过包装类,什么是包装类,在面试常问的dubbo的spi机制到底是什么?(上)有说过,一个一个构造,慢慢构成一个调用链条,最终才会调用到真正的实现类

我们看看源码的实现

@Wrapper注解是个匹配的作用,就是根据需要属性从包装类中选择一批可以用来包装的类。

构造其实很简单,就是当前instance当做包装类的构造参数通过反射构造,然后进行依赖注入,然后将构造出来的对象复制给instance,instance再进行回调之后再赋值给instance,这样往往复复就形成了一个链条。这里我画个图,让大家看看最后构造出来的对象是什么样。

构造后的对象其实就是这样,你最终使用的对象其实是包装对象,如果你获取对象的时候传的wrap参数是true的话,当前默认情况下是true。最后调用的话就会先调用最外层的包装的方法(包装对象2),然后调用(包装对象1)一直调用,最后会调用到真正的目标对象的方法。

为什么需要包装?

很多人可能不清楚,为什么需要包装,其实很好理解,就是起到动态增强目标对象的作用。可以理解为spring中的aop,但是dubbo因为不像spring那样有完整的ioc和aop的实现,dubbo就通过这种包装的方式来实现动态增强目标对象功能的作用。

第七步:Lifecycle接口回调

接下来会调用initExtension方法,这个方法的作用就是判断你的实现类有没有实现Lifecycle接口,如果有的话会调用initialize()方法的实现

至此,一个可用的实现类对象就算完完全全构建完成了,你拿到的对象就是这个对象,然后就会返回这个对象,存到Holder对象中。

最后来张图总结一下实现类构造的过程。

这里我在简单说明一下,

1)包装不是必须的,得看你要获取的对象是什么,如果不要包装,就会回调原始对象的Lifecycle接口,不过dubbo内部的框架基本上获取的都是带包装的对象,而非原始的对象;

2)包装时暴露出去的是包装类的对象,在调用的时候,最先调用的也是包装类的对象,然后一层一层的调用,最终调用到实现类对象。

二、自适应机制

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

自适应对象获取的方法,就是getAdaptiveExtension方法。构建自适应对象的方法就是createAdaptiveExtension方法的实现。

源码很简单,就是得到自适应的实现类,然后就是普通反射构造,然后经过初始化前,依赖注入,初始化之后,Lifecycle接口回调操作,构造出对象。

自适应的类有两种来源,一种是自己在实现类上加@Adaptive注解,指定自适应实现类,上面提到的AdaptiveExtensionInjector就是指定的自适应实现类,类上加了@Adaptive注解,如果不指定,dubbo框架会按照一定的规则来动态生成一个自适应的类,构造过程在createAdaptiveExtensionClass方法实现,最终会调用AdaptiveClassCodeGenerator生成代码

三、自动激活

所谓的自动激活,就是根据你的入参,动态的选择一批实现类返回给你。至于怎么找到,就是通过注解@Activate来实现的。dubbo内部自动激活的主要用在Filter中,Filter是个接口,有很多实现。不论是在provider端还是consumer端,在调用之前,都会经过一个由Filter实现构成的链,这条链的不同实现就是根据入参的不同来区分是每个Filter的实现属于provider的还是consumer端的。

@Activate它有三个重要属性,group 表示修饰在哪个端,是 provider 还是 consumer,value 表示在 URL参数中出现才会被激活,order 表示实现类的顺序。

总结

本文接着上篇 面试常问的dubbo的spi机制到底是什么?(上)从源码的角度剖析了dubbo spi机制的功能,包扩了在构建对象时的ioc和自动包装的机制、自适应对象机制、自动激活机制。整体而言,dubbo的spi机制不是很难,所以大家看了两篇文章之后如果自己再过一遍源码的话那么收获会更大。dubbo的spi机制其实非常重要,如果不理解dubbo的spi机制的特性的话,在阅读dubbo源码的时候,很难读懂,因为你可能都不知道,你拿到的对象到底是什么样的,这样就很难理解一些功能的实现。

0 人点赞