概述
一. SPI定义
- SPI 全称为 Service Provider Interface,一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如mysql-connector包、spring-web包等,Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。
- 面向的对象的设计里,模块之间是基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候,不在模块里面写死代码,这就需要一种服务发现机制。java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到代码之外(配置文件中)。
- jdk的spi的具体约定如下 :当服务的提供者(provider),提供了一个接口多种实现时,一般会在jar包的META-INF/services/目录下,创建该接口的同名(相对路径加文件名称)文件。该文件里面的内容就是该服务接口的具体实现类的名称。而当外部加载这个模块的时候,就能通过该jar包META-INF/services/里的配置文件得到具体的实现类名,并加载实例化,完成模块的装配。
二. dubbo为啥不使用jdk的spi
- jdk的spi会一次性加载所有的接口实现,如果有扩展实现初始化很耗时,没有使用的实现也会加载,耗费资源
- jdk的spi没有对IOC和AOP的支持,dubbo增强的spi增加了对扩展点IOC和APO的支持,一个扩展点可以直接setter注入其他扩展点
- 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因
三. dubbo SPI的实现
- dubbo使用spi的目的就是获取一个实现类的实现对象,获取途径:
org.apache.dubbo.common.extension.ExtensionLoader#getExtension(java.lang.String)
- dubbo的spi实现路径:a):
org.apache.dubbo.common.extension.ExtensionLoader#getExtensionLoader
这个就是给入参Class接口创建一个ExtensionLoader
并缓存到map里面;b):org.apache.dubbo.common.extension.ExtensionLoader#getAdaptiveExtension
这个是获取一个扩展装饰类的对象,这个类有个规则是:如果他没有一个@Adaptive
注解,就动态创建一个装饰类,比如Protocol$Adaptive
对象;c):org.apache.dubbo.common.extension.ExtensionLoader#getExtension(java.lang.String)
这个就是获取或者创建一个对象
下面通过代码讲这三个方式的源码,对应下面的1、2、3小节
四. 代码举例
- dubbo代码开始是在:
上面三个图可以看出实际是创建了ExtensionLoader
并加入到了org.apache.dubbo.common.extension.ExtensionLoader#EXTENSION_LOADERS
缓存起来 - 那么实际重点是在:
org.apache.dubbo.common.extension.ExtensionLoader#getAdaptiveExtension
进入这个org.apache.dubbo.common.extension.ExtensionLoader#createAdaptiveExtension
方法一步一步跟进:
org.apache.dubbo.common.extension.ExtensionLoader#getAdaptiveExtensionClass
↓
org.apache.dubbo.common.extension.ExtensionLoader#getExtensionClasses
↓
org.apache.dubbo.common.extension.ExtensionLoader#loadExtensionClasses
↓
org.apache.dubbo.common.extension.ExtensionLoader#loadDirectory(java.util.Map<java.lang.String,java.lang.Class<?>>, java.lang.String, java.lang.String, boolean, boolean, java.lang.String...)
↓
org.apache.dubbo.common.extension.ExtensionLoader#loadResource
从代码可以看到SPI发现接口实现实际是通过:得到配置文件的全路径,然后通过ClassLoader
获取到Enumeration<java.net.URL> urls
,通过java.net.URL.openStream()
加载class
然后继续看上面代码过程,从这些代码跟进中会发现其实他是把SPI中的实现全部加载进了缓存中。
有注解:org.apache.dubbo.common.extension.Adaptive
的赋值到了org.apache.dubbo.common.extension.ExtensionLoader#cachedAdaptiveClass
否则:构造函数入参是传入参数clazz的set到了:org.apache.dubbo.common.extension.ExtensionLoader#cachedWrapperClasses
,这个cachedWrapperClasses
里面存放的就是几个Wrapper类,这个是后面SPI的AOP使用的
其余的根据条件加入各自缓存容器里
代码截图:
这里就是dubbo的SPI基本用法,dubbo中的接口实现很多都是使用SPI的方式加载的
同时上面图这里也有一个细节,通过代码可以看到org.apache.dubbo.common.extension.ExtensionLoader#getExtensionClasses
方法里面会把(上面写到了)@Adaptive
注解的赋值到cachedAdaptiveClass
,那么如果根据判断clazz.isAnnotationPresent(Adaptive.class)
没有@Adaptive
注解则这个cachedAdaptiveClass
为空,那么就会进入org.apache.dubbo.common.extension.ExtensionLoader#createAdaptiveExtensionClass
,这个方法的作用就是动态生成一个Adaptive代理类,具体生成代码在:org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator#generate
,生成代码之后会动态编译这个生成的代码,并返回一个编译后的Class,这里主要是动态编译相关内容,在我的后面一篇中专门讲了这个:dubbo的动态编译javassist,大家可以去看看
所以根据上面这代码可以得出一个结论:org.apache.dubbo.common.extension.ExtensionLoader#getAdaptiveExtension
这个方法会获取一个扩展类的对象,如果这个类有@Adaptive
注解,那么通过类编码的内容获取到,如果没有@Adaptive
注解,那么就动态生成一个代理类,所以总结起来就是:如果@Adaptive
注解在类上,那么这个类就是一个装饰类,如果注解在方法上就是一个自动生成的动态代理类
例如:
回到:
可以看到在getAdaptiveExtensionClass()
获取到Class之后调用了java.lang.Class#newInstance
创建实例,然后进入了org.apache.dubbo.common.extension.ExtensionLoader#injectExtension
,这个方法其实是dubbo的IOC实现,这个在下面讲 - 上面讲了
org.apache.dubbo.common.extension.ExtensionLoader#getAdaptiveExtension
,最后讲到了org.apache.dubbo.common.extension.ExtensionLoader#injectExtension
这里停下来了,这里我们从org.apache.dubbo.common.extension.ExtensionLoader#getExtension(java.lang.String)
方法切入查看,举例:ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("injvm")
从这图中可以看出,根究传入的name,先从缓存中获取Holder
,如果Holder
实例为空那么创建一个进入:org.apache.dubbo.common.extension.ExtensionLoader#createExtension
这个方法可以看到刚进来就是getExtensionClasses()
,这个就是上面我们已经看过的加载所有SPI实现的方法,然后根据获取的Class又是一套缓存获取没有就创建,然后把创建的实例带着进入:org.apache.dubbo.common.extension.ExtensionLoader#injectExtension
,这个方法就是上面我们停下来的地方,这里我们接着分析这个方法
这个方法进来就是循环这个实例的所有方法,看方法是不是set方法("set"开头、public、并且只有一个入参)、是不是标注有@DisableInject
注解、参数是不是基本数据类型的,如果满足其中一个就不循环直接下一个,重点看这个方法:objectFactory.getExtension(pt, property)
,这个objectFactory
其实是org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
,他里面有个属性:org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory#factories
是个List,这个List里面其实就是:org.apache.dubbo.common.extension.factory.SpiExtensionFactory
和org.apache.dubbo.config.spring.extension.SpringExtensionFactory
,通过org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory#getExtension
方法分别从SPI和Spring里面获取实例
这里我们先看SpiExtensionFactory
的时候执行的代码
这段代码其实就是我们之前分析的ExtensionLoader.getExtensionLoader(type)
和org.apache.dubbo.common.extension.ExtensionLoader#getAdaptiveExtension
,从SPI里面获取实例对象,这里就不再重新说一遍了。接着再看SpringExtensionFactory
的
其实就是从spring容器ApplicationContext
中获取实例对象,当然如果这个实例标注有@SPI
注解,那么不从spring中获取,应该从SPI中获取。到这里就分析完了获取实例的部分了,然后回到
获取到实例之后因为这里是set方法,通过method.invoke(instance, object)
进行注入,这里就是dubbo的IOC依赖注入,接下来继续回到org.apache.dubbo.common.extension.ExtensionLoader#createExtension
中来,接着往下走
从if(wrap)
这个进入之后,先是找到实现这个SPI接口的所有wrapper类,对其根据org.apache.dubbo.common.extension.support.WrapperComparator#compare
规则进行排序并反转顺序,那么这个org.apache.dubbo.common.extension.ExtensionLoader#cachedWrapperClasses
里面存放的是什么呢,其实上面在分析SPI根据配置文件加载对象Class的时候看到这个了org.apache.dubbo.common.extension.ExtensionLoader#loadClass
这个方法,
意思是构造函数入参是这个实例Class的时候,就认为他是个wrapper类,比如
那回到org.apache.dubbo.common.extension.ExtensionLoader#createExtension
方法中,接着讲在拿到warpper类之后,会执行:injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance))
这行代码,这行代码的意思是获取这个warpper类的构造器,并创建实例,然后继续调用上面我们刚分析过的dubbo的IOC方法org.apache.dubbo.common.extension.ExtensionLoader#injectExtension
,把这个wrapper实例进行依赖注入,其实就是AOP,加入了过滤和监听事件(这几个Wrapper类,后面在dubbo服务发布这篇里面有提到这几个),之后就是经过这行代码再把创建的实例return出去
到这里就返回了一个包装过的对象实例,这就是dubbo的SPI通过getExtension
方法获取实例的过程
最后
以上就是现代金毛为你收集整理的dubbo的SPI的全部内容,希望文章能够帮你解决dubbo的SPI所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复