Dubbo SPI
2021-11-21 19:30:23 1 举报
AI智能生成
SPI定义,jdk SPI 原理,Dubbo SPI原理
作者其他创作
大纲/内容
SPI
定义:Service Provider Interface,是一种服务发现机制,在运行期间寻找接口的实现类,用来组件扩展和替换
底层框架定义接口,使用SPI获取实现类并使用,上层应用可以为接口提供实现,以扩展或改变底层框架的行为
jdk SPI机制
例如jdk的java.sql.Driver数据库驱动接口,DriverManager类加载时,会使用ServiceLoader加载具体数据库厂商提供的驱动实现
使用:框架使用ServiceLoader获取接口实现类,是一个集合,并使用,应用层提供接口实现,并在类路径的META-INF/services/目录创建名为接口全限定名的文件,内容每一行是提供的实现类全限定名
原理:ServiceLoader实现了Iterable,迭代时,使用类加载器读取META-INF/services/+接口全限定名的文件,将每一行的实现类全限定名读出来,Class.forName加载类得到class对象,最后newInstance实例化
迭代时使用了懒加载的方式,首次迭代后,将实例化对象添加到LinkedHashMap中,后续直接从中获取
缺点
不能按需加载,ServiceLoader是一个迭代器,如果要得到其中一个实现需要依次遍历
多线程环境下每个线程都会创建各自的ServiceLoader,不能复用
Dubbo SPI
dubbo没有使用jdk SPI机制,而是重新定义了自己的,功能更丰富,拥有按需加载、IOC和AOP等特性
使用
定义接口并用@SPI标注,在META-INF/dubbo路径下创建名为接口全限定名的spi文件,并在其中每一行声明扩展类名称和扩展类全限定名,等号分割
工厂方法获取接口对应的ExtensionLoader对象,然后通过这个ExtensionLoader实例对象指定扩展类名称获取扩展类实例
先创建ExtensionLoader实例并缓存在ConcurrentHashMap中,每个spi接口对应一个ExtensionLoader实例.
ExtensionLoader.getExtension(name)根据扩展名称创建并缓存扩展类实例,先getExtensionClasses加载所有扩展类class对象并缓存起来,再根据扩展名称实例化相应扩展实例
扩展类的声明文件查找路径:依次从
META-INF/dubbo/internal,
META-INF/dubbo,
META-INF/services路径查找
META-INF/dubbo/internal,
META-INF/dubbo,
META-INF/services路径查找
每个ExtensionLoader实例内部维护了一个名称到扩展类class对象的map,首次访问时接口的所有实现类class对象会全部加载并缓存,但只会实例化传入name的扩展类
关于线程安全
不同的ExtensionLoader互不影响线程安全性,ExtensionLoader缓存在ConcHashMap中,通过putIfAbsent+get保证不覆盖缓存,获取到的是同一个对象
同一个ExtensionLoader的成员变量,通过ConcHashMap+Holder保证线程安全,Holder持有了带创建对象,先创建Holder,再对Holder加锁,好处是:1、减小了锁的粒度,粒度在待建对象的级别。2、避免线程重复创建对象,因为直接putIfAbsent+get会重复创建
computeIfAbsent感觉也可以,代码实现更简洁,但锁粒度更粗些,在哈希槽级别,不同的key可能会碰撞
IOC
IOC注入只支持setter注入,注入对象是ExtensionFactory实例创建的,它本身又是一个SPI接口,具体类是AdaptiveExtensionFactory,它会依次使用SpiExtensionFactory和SpringExtensionFactory注入
注入SPI扩展
实际上注入的是适配器,适配器也是服务接口实例,会对接口中标注了@Adaptive的、有一个参数是URL类型方法进行适配,适配的过程是从url对象的parameters获取到扩展名称,在ExtensionLoader.getExtension获取到扩展实例并调用
@Adaptive标注在方法上,适配器是动态编译生成的;标注在类上,被标注的类是适配器
只支持setter注入,SPI接口中需要至少一个@Adaptive标注的、有一个参数是URL类型的方法,setter注入的实际上是动态编译生成的xx$Adaptive类实例,该类的@Adaptive方法会根据URL的parameters获取真正的要注入的扩展点名称,并通过ExtensionLoader.getExtensionLoader(Interface.class).getExtension(extName)获取并调用被适配的方法
@Adaptive.value可以指定适配器的扩展名称在URL.parameter的key
@Adaptive.value如果是protocol,扩展名称就是URL.protocol
动态生成的适配器的特点
包名和服务接口包名一致
类名=接口名+$Adaptive
没有@Adaptive标注的方法抛UnsupportedOperationException
@Adaptive方法必须有一个参数为URL,或者有参数对象的方法返回值是URL
子主题
@Adaptive也可以用在类上,用来自定义适配器, 比如动态编译适配器的Compiler,它的适配器只能用这种方法创建
适配器也会依赖注入
注入Spring bean
根据setter方法名称的属性名称和参数的类型,从spring applicationcontext中获取注入bean
applicationcontext是SpringExtensionFactory静态方法加入的
AOP
Dubbo的AOP是编译期AOP,通过包装器实现的,需要定义一个或多个包装器类,包装器也是服务接口的实现类,并且有一个参数类型是服务接口类型的构造器
loadClass加载扩展类时,如果是包装器类,会加到cachedWrapperClasses集合中,扩展点IOC后,会把扩展点作为构造函数参数实例化包装器,并依次包裹
包装器可以定义多个,这样就有多层包装,@Activate(order)更小的包在外层
包装对象也会进行依赖注入
过滤扩展
Dubbo SPI支持根据组名,扩展点名称,以及url参数一次性获取多个扩展点
组名和扩展点名称是“或”的关系,只要一个符合条件就行
url的参数和组名是“与”的关系
url的参数的key和@Activate.value匹配上的,则符合条件
0 条评论
下一页