上一篇我们看过了jdk中的spi机制,也分析了它的缺点就是会一次性将META-INF/services下的配置文件中,对应接口的全部实现类都给加载;
而dubbo中的spi肯定是提高了性能,还扩展了原生的spi(这就是一句废话,如果提高性能和没有扩展新的功能,干嘛不用原生的啊(-_-メ))
1. 基于dubbo的spi栗子
我们先说说提高性能,一般提高性能肯定就是使用到了缓存嘛,没有什么比缓存更能提高性能的了;
啥也不管,先举个例子看一看,在上一篇的基础上,导入dubbo依赖
<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>2.7.5</version> </dependency>
目录结构如下:
一个接口和两个实现类:
@SPI public interface ISayNameDubbo { void say(); } public class SayChineseNameDubbo implements ISayNameDubbo { @Override public void say() { System.out.println("dubbo 中文:哈喽,你好帅呀๑乛◡乛๑"); } } public class SayEnglishNameDubbo implements ISayNameDubbo { @Override public void say() { System.out.println("Dubbo English:hello cool java boy"); } }
配置文件:
执行结果:
使用方法跟java原生的spi几乎一样,只不过有两个地方需要注意:
1.接口要加上@SPI注解
2.配置文件是放在META-INF/dubbo下面,而且文件内容是以键值对的形式
2. dubbo中spi机制分析
根据上面的例子, 我们知道首先使用ExtensionLoader.getExtensionLoader(ISayNameDubbo.class)获取到指定接口的ExtensionLoader,我们看看这个ExtensionLoader是个啥?╮(╯_╰)╭
下图中缓存的操作,可以看到ExtensionLoader其实就是对我们接口的封装,然后调用这个封装类的方法,获取该接口我们指定的实例
先看看校验是否有SPI注解的方法:withExtensionAnnotation
下图所示,其实就是做了一个判断,后面我们会再看看这个注解中的值,还有啥所用
接下来我们就看一下ExtensionLoader的getExtension方法,就能大概知道是怎么获取指定的实例的了;
其实也可以根据之前使用的java的SPI机制猜一下,大概的思路就是先去META-INF目录下找到指定的配置文件,然后根据getExtension(String key)方法传入的key去找到对应的实现类的全路径,最后使用反射进行实例化就完成了
然后我们看看代码是不是这样实现的
首先我们看看getOrCreateHolder方法,其实使用了一个缓存,用于缓存key->Holer, 至于Holder的实例作用,你点开看看就知道它里面有个volatile关键字,很明显这里是为了协助我们使用单例模式的,保证一个SPI修饰的接口的实现类是单例的;
注意,下图这种缓存的操作后面能看到很多(所以说dubbo中的SPI提升了性能๑乛◡乛๑)
我们再接着看createExtension方法,我们重点看看getExtensionClasses()方法
下面就是加载那三个路径下,实现类的配置文件,以key->value的形式保存起来
如果想继续看看怎么解析配置文件的,可以看看loadDirectory()方法:
3.总结一下
其实还有很多东西没说,比如dubbo中的ioc和aop,其实能说很多的,但是我这里就是不要说了,先不要太深入了;
总结一下,dubbo的SPI的逻辑几乎都在ExtensionLoader中,而且一个@SPI修饰的接口,对应这一个ExtensionLoader实例,我们再根据配置文件中的key,调用ExtensionLoader实例的getExtension方法,这里会使用缓存和Holder来控制实例化,使得每个接口的实现类只会被实例化一次;
在实例化的时候调用createExtension()方法,这里会首先加载"META-INF/services/","META-INF/dubbo/","META-INF/dubbo/internal/"三个目录中的所有配置文件,解析出来所有的key->value,并且将value使用Class.forName方法转为Class实例,保存到缓存中,并且根据返回我们需要的Class对象,使用反射实例化对象;
此时对应的接口的实例化对象已经好了,但是对象里面的属性值还是空的,所以还需要使用dubbo的IOC机制,有兴趣的可以看看injectExtension方法,objectFactory就是IOC对象(类似spring的IOC容器,可以提供很多的对象),并且还用了AOP的机制,这句代码:instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
不过我的建议是暂时先不要管IOC和AOP,等了解多了再回头看看,会容易理解很多