Reference:
前记
其实SPI原理是很简单,在MET-INF文件下,创建文件名是一个接口类,内容是实现类。然后可以实现动态加载类。但是在dubbo中SPI具体会怎么应用,以及和filter的联动。在面试中也有一些问题
dubbo的成功离不开它采用 为内核设计+SPI扩展,使得有特殊需求的接入方可以自定义扩展,做定制的二次开发。依靠SPI机制实现了插件化功能,几乎将所有的功能组件做成基于SPI实现,并且默认提供了狠毒偶可以直接使用的扩展点,实现了面向功能进行拆分的对扩展开放的架构
什么是SPI
SPI(Service Provider Interface)主要是用来在框架中使用,最常见的就是访问数据库时用到的java.spi.Driver接口了。首先需要定制一个接口,数据库厂商需要根据接口来开发他们对应的实现。大家都约定好将实现类的配置放在一个地方,然后到时候去那里查一下就知道了。java SPI就是这样做的,约定在Classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,然后文件里面记录的是此JAR包提供的具体实现类的全限定名。当我们引用了某个jar包的时候就可以去找这个jar包的META-INF/services/目录,再根据接口名找到文件,然后读取文件里面的内容去进行实现类的加载和实例化
比如mysql:
java SPI源码分析
ServiceLoader.load()就是java SPI 的入口
简单的说就是先找到当前线程绑定的ClassLoader,如果没有就用SystemClassLoader,然后清除一下缓存,再创建一个LazyIterator。LazyIterator其实就是Iterator的实现类
可以看到这个方法其实就是在约定好的地方找到接口对应的文件,然后加载文件并且解析文件里的内容
再来看下 nextService()
所以就是通过文件里填写的全限定名加载类,并且创建其实例放入缓存之后返回实例。
整体的java SPI的源码解析已经完毕。就是约定一个目录,根据接口名去那个目录找到文件,文件解析得到实现类的全限定名,然后循环加载实现类和创建其实例
java SPI 哪里不好
java SPI 在查找扩展实现类的时候遍历了SPI的配置文件并且将实现类全部实例化,假设一个实现类初始化过程比较消耗资源且耗时,但是代码里面又用不上他,就产生了资源的浪费。java SPI 无法按需加载实现类
Dubbo SPI
dubbo 中 SPI 可以按需加载,配置文件里面放的是键值对
并且dubbo SPI 除了可以按需加载实现类之外,增加了IOC和AOP的特性,还有各自适应扩展机制
dubbo对配置文件目录的约定,不同于java SPI 主要分为三类目录
- META-INF/services/ 兼容java SPI
- META-INF/dubbo/ 用户自定义的SPI
- META-INF/dubbo/internal/ dubbo内部使用的SPI
DUBBO SPI简单实例
Dubbo 源码分析
版本 2.6.5
上面代码中 ExtensionLoader 是入口
做了一些判断然后从缓存里面找是否已经存在这个类型的 ExtensionLoader。如果没有就新建一个塞入缓存。最后返回接口类对应的 ExtensionLoader
再看一下 getExtension() 方法
先找到实现类,判断缓存是否有实例,没有就反射建一个实例,然后执行set方法依赖注入、如果找到包装类的话,再包一层
下面再细说一下
getExtensionClasses
getExtensionClasses()这个方法进入也是先从缓存中找,如果是空的,那么调用 loadExtensionClasses
而 loadDirectory 里面就是根据类名和指定的目录,找到文件先获取所有资源,然后一个一个去加载类,再通过 loadClass 去做一下缓存操作
loadClass 之前已经加载了类,loadClass 只是根据类上面的情况做不同的缓存,分别由 Adaptive WrapperClass 和普通类这三种,普通类又将 Activate 记录了一下。至此对于普通类来说整个SPI过程完结
Adaptive 注解 - 自适应扩展
首先需要知道dubbo的自适应扩展机制:根据请求的参数来动态选择对应的扩展
dubbo通过一个代理机制实现了自适应扩展,简单的说就是为你想扩展的接口生成一个代理类,可以通过JDK或者javassist编译你生成的代理类代码,然后通过反射创建实例
这个实例里面的实现会根据本来方法的请求参数得知需要的扩展类,然后通过 ExtensiveLoader.getExtensionLoader(type.class).getExtension(从参数得来的name) 来获取真正的实例来调用
源码是怎么做到的
这个注解就是自适应扩展相关的注解,可以修饰类和方法,在修饰类的时候不会生成代理类,因为这个类就是代理类,修饰方法上的时候会生成代理类
ADAPTIVE 注解在类上
比如 ExtensionFactory 有三个实现类,其中一个实现类就被标注了 Adaptive 注解
在 ExtensionLoader 构造的时候会通过 getAdaptiveExtension 获取指定的扩展类的 ExtensionFactory
可以看到先缓存了所有实现类,然后再获取的时候通过遍历找到对应的Extension
再来深入分析一波 getAdaptiveExtension