前言
本来是在写Dubbo的消费者初始化过程源码解析的,结果源码看着看着发现SPI还是其中一个比较重要的知识点,因此先跳出来单独写一篇关于Dubbo中SPI的。
什么是SPI
这里不准备讲太多,简单说一下我所知道的JDK中的SPI技术,大概描述一下就是,一个接口I有多个实现类A、B、C,我可以通过ServiceLoader.load(I.class)这样加载接口的方式得到它的实现类列表,而我具体需要加载A、B、C中的哪几个不需要硬编码在代码中,而是可以在META-INF下的某个文件中配置。有一个大概的概念之后,我们开始进入正题来看Dubbo中的SPI使用。
源码解析
在Dubbo中的ReferenceConfig类有如下的常量定义,这就是Dubbo中典型的SPI应用
1 | private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); |
接下来以proxyFactory对象的生成过程进行分析。首先来看一下getExtensionLoader(Class
1 | @SuppressWarnings("unchecked") |
这里的EXTENSION_LOADERS是一个ConcurrentMap,key和value的定义如下
1 | private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>(); |
从源码可以看出getExtensionLoader方法主要就是根据传入的类型从EXTENSION_LOADERS找到对应的ExtensionLoader对象,如果不存在就通过new ExtensionLoader
1 | private ExtensionLoader(Class<?> type) { |
代码量比较少,首先将type设为一个全局的变量,然后的流程是当传入的参数类型为ExtensionFactory时,objectFactory赋值为null,否则递归调用ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()方法给objectFactory赋值。那么需要来看一下getAdaptiveExtension()方法了。
1 | @SuppressWarnings("unchecked") |
可以看到这里是一个很经典的双重检查锁的单例模式实现,保证了instance对象的单例,慢慢来分析。首先看一下cachedAdaptiveInstance对象和它的类定义
1 | private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>(); |
这里的Holder对象其实就是对volatile修饰的变量value进行了一层封装,跳回去继续看getAdaptiveExtension()方法。其中当instance确实为空时调用了createAdaptiveExtension()方法生成初始值
1 | @SuppressWarnings("unchecked") |
这个方法只调用了injectExtension(T instance)方法,传入的参数是 getAdaptiveExtensionClass().newInstance()。接下来先分析一下入参中的getAdaptiveExtensionClass()方法
1 | private Class<?> getAdaptiveExtensionClass() { |
这里只是为了返回一个Holder类型的cachedAdaptiveClass对象,调用getExtensionClasses()后,若cachedAdaptiveClass不为null直接返回,否则返回createAdaptiveExtensionClass()创建的对象。
1 | private Map<String, Class<?>> getExtensionClasses() { |
又一个双重检查锁,一看就知道核心是loadExtensionClasses()方法
1 | private Map<String, Class<?>> loadExtensionClasses() { |
抽丝剥茧终于来到了SPI机制最核心的部分了。首先final SPI defaultAnnotation = type.getAnnotation(SPI.class)做的就是收集所有包含@SPI标签的类,并且默认值赋值给cachedDefaultName,因为getAnnotation方法内部调用比较深加之是JDK中的代码就不在这里展开了。这个方法中有三个比较重要的常量,这几个目录下的文件就是类似之前提到的JDK中SPI的配置文件,但格式上稍有一些不同,这个后面讲。
1 | private static final String SERVICES_DIRECTORY ="META-INF/services/"; |
loadFile(Map<String, Class<?>> extensionClasses, String dir)的源码实在有些长,稍微精简一下去掉了所有的catch和finally分支以后是这样
1 | private void loadFile(Map<String, Class<?>> extensionClasses, String dir) { |
仔细看会发现其中还留了一个catch的分支,特意留着没删是因为有且仅有在这个分支当中才使用到了入参extensionClasses。也就是说仅有当clazz.getConstructor(type)方法抛出NoSuchMethodException异常时才会检查extensionClasses并向其中添加数据。
这个分支稍有些难懂,于是查阅了一些资料,这里大概的想法是通过否有@Adaptive
注解和是否有指定参数的构造方法,将clazz分为了三类,分别放到cachedAdaptiveClass
,cachedWrapperClasses
和extensionClasses
中。cachedWrapperClasses
中存的通常是ProtocolFilterWrapper
和ProtocolListenerWrapper
这样的类。
整个方法基本上就是两层while循环,外层是遍历dir目录下文件的(其实不太理解为什么需要这层循环,只能认为是通过type指定了文件名但没有指定后缀),内层是逐行解析文件中的配置,并通过反射的方式加载到内存并添加到extensionClasses中。另外该方法中顺带在cachedActivates、cachedNames这两个map中也添加了相关类信息。
这里顺带看一下META-INF/dubbo/internal下文件的片段,以其中的com.alibaba.dubbo.rpc.ProxyFactory文件为例,其中配置如下
stub=com.alibaba.dubbo.rpc.proxy.wrapper.StubProxyFactoryWrapper
jdk=com.alibaba.dubbo.rpc.proxy.jdk.JdkProxyFactory
javassist=com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory
这里与JDK中的SPI配置不同之处在于,JDK中每行直接写类名,而Dubbo中则是采用了key=value这种形式,这里的key同样也是extensionClasses中存储数据时的key。
接着一路回退到getAdaptiveExtensionClass()方法,在执行完getExtensionClasses()后,若cachedAdaptiveClass中已经填充了值,它的值是Class.forName(line, true, classLoader)初始化的类,返回该值;若cachedAdaptiveClass仍为null,cachedAdaptiveClass = createAdaptiveExtensionClass()填充。
1 | private Class<?> createAdaptiveExtensionClass() { |
这里的createAdaptiveExtensionClassCode()又是一个200多行的超长方法,实在是贴不下了。。。老实说这个方法真的是让我感到震惊,虽然返回的是个String,但这个生生由StringBuilder拼出来的返回值包含了一整个类的定义,然后再通过compiler.compile(code, classLoader)加载成一个类,从而完成了对接口的动态代理。比如Protocol接口生成的代理类如下
1 | public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol { |
这里有一块值得注意的是
1 | com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); |
方法具体的实现是由getExtension(String name)方法实现的,入参extName默认是之前赋值的cachedDefaultName的值。具体看一下getExtension(String name)方法
1 | @SuppressWarnings("unchecked") |
该方法就是从cachedInstances中拿值,若为空则最终返回createExtension(name)方法的结果
1 | @SuppressWarnings("unchecked") |
首先看一下injectExtension(T instance)方法
1 | private T injectExtension(T instance) { |
方法中判断条件是objectFactory,很眼熟吧,最开头的ExtensionLoader中有提到过,那么这里的objectFactory值就该是非null,for循环遍历了实例中所有的单参public的set方法去执行,是不是和Spring中的set注入很像,再回过头看看方法名一下子恍然大悟,而方法最后返回的仍然是入参对象。
回到createExtension(name)方法,可以看到这里最终返回的是instance这个对象,而这个对象做为入参调用了多次injectExtension(T instance)方法,其实第一次调用已经完成了初始化,那么后面的for循环中调用是在做什么呢。
这里出现了cachedWrapperClasses
,还记得这个属性吗,在之前的loadFile方法中有往其中设值,它是一个ConcurrentHashSet<Class<?>>()对象,里面放的就是配置文件中解析后加载的Wrapper类,而Wrapper类的特点就是含有以@SPI标签接口类型为参数的构造方法,那么这里的意思就很明显了,以instance做为Wrapper类的构造方法的参数生成Wrapper类的实例。以Protocol的默认实现为例的话,最后生成的instance对象将会是ProtocolListenerWrapper
的实例,其protocol属性为ProtocolFilterWrapper
的实例,而这个ProtocolFilterWrapper
实例的protocol属性为DubboProtocol
的实例,这样就形成了一种链式的关系。
总结
1 | private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); |
到这里我们已经把proxyFactory如何通过SPI加载的过程都串了一遍,总的来说就是通过META-INF/dubbo/internal下的配置文件加载所有的ProxyFactory实现类,最后通过用户选择动态生成ProxyFactory的一个代理链。这种方式可以很灵活的实现ProxyFactory实现类的增加,而且该修改与代码解耦,有很好的扩展性。