一步步解析Dubbo之@SPI机制

(我的理解,若是有误,望请指正,谢谢)
首先@SPI机制的功能类是ExtensionLoader.这是一个泛型,这个类中有两部分构成,一部分是静态变量,一部分是实例变量。
静态变量中主要有两个CurrentHashMap,
在这里插入图片描述
一个用来存已经实例化的ExtensionLoarder,一个则用来存放具体类型的实例
接下来,咱们来看下实例变量部分:
有一个class type,和ExtensionFactory objectFactory;
其中ExtensionFactory objectFactory也是经过
objectFactory = (type == ExtensionFactory.class ? null :ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
这种方式获取的,因此当实例化类的时候会先初始化objectFactory,但当objectFactory为空的时候,会建立objectFactory的扩展对象,这个时候type==ExtensionFactory,因此,objectFactory的objectFactory是空的,这样就不会出现循环的建立了。
此时能够看到当我new 一个新的Extension的时候实际上是会建立了两个(前提是objectFactory没有被实例化过),这个时候Extension_LOADERS中就会 有两个Extension,一个是type的ExtensionLoader,一个objectFactory的ExtensionLoader
接下来,来看下getExtensionLoader(Class type)
在这里插入图片描述
到这里,我看到,首先会判断type类型是否为空,是不是接口,是否有注解@SPI,而后获取当前已经生成的extension里是否已经有了,没有的话新建一个而后放到Extension_LOADER,这个时候新建的是一个空的ExtensionLoader类。
再来看看getAdaptiveExtension
在这里插入图片描述
这个方法调用是被具体已经实例化的空壳的ExtensionLoader(首次调用),首先查看有没有建立异常createAdaptiveInstanceError,由于当instatnce为null的时候,会从新去建立,可是若是上次建立是空的,那instance是空的,防止这种重复操做,当建立失败的时候,createAdaptiveInstanceError就会被更改为true,那下次的时候就直接判断失败不会去重复执行建立了
当没有建立过期候,那开始建立,这里的实例都是单例模式,防止要建立多个实例,因此首先这里要锁住实例缓存,这里用了双重检锁,通常的双重检锁是有并发问题的(这里主要涉及并发下的重排序),这里的解决方案是将设置成final,cacheAdaptiveInstance是final类型。而后继续往下读代码,当instance仍旧是空,就说明真的没有初始化,须要建立实例,调用了createAdaptiveExtension()方法进行实例化,实例化成功后,会将结果放到cacheaAdaptiveInstatnce中。
下一步,咱们来了解下createAdaptiveExtension()方法怎么建立实例的
在这里插入图片描述
这个比较简单,主要是从getAdaptiveExtensionClass方法返回一个class,而后实例化,再将其传给injectExtension进行注入。
先看里面的getAdaptiveExtensionClass()这个方法
在这里插入图片描述
先调用getExtensionClasses来获取class,再判断cachedAdaptiveClass是否是为空,不为空直接返回,为空的话还须要调用createAdaptiveExtensionClass();
getExtensionClass()的实现是
在这里插入图片描述
看到这里,又有双重检锁了,一样这里用了final,若是cachedClasses中没有则执行loadExtensionClasses();
(cachedClasses是一个实例变量,被final修饰,private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>()?,到这里估计已经看晕了,是有点绕。接下来仍是要看下loadExtensionClasses()
在这里插入图片描述
cacheDefaulExtensionName()这个方法是查看@SPI标签里有没有指定默认的名称的,有的话默认取第一个
再来分析loadDirectory方法,其实从字面意思,咱们能看出这个是用来加载文件夹的,咱们先来看下这几个加载的文件路径吧:
在这里插入图片描述
看着是否是有点熟悉扩展文件通常都放在这些目录下
在这里插入图片描述
这里最重要的方法是loadResource,载入资源的
在这里插入图片描述
这个方法有点长,可是也是不难理解,其实就是按行进行解析文件,解析完之后,交给loadClass来作最后的解释
在这里插入图片描述
首先判断这个class是否是接口,不是直接报错,接着判断是否是有Adaptive注解,有的话直接执行cacheAdaptiveClass(),其实就是将cacheAdaptiveClass值设置成当前class,若是没有Adaptive注解,再判断是否是包装类,有包含当前type的构造函数的,为包装类,代码就不贴了,而后将值放到cacheWrapperClass的set里。还不是,那执行最后一步,先获取类的构造方法,(这里若是name为空会去获取当前类的Extension注解,从中获取name,可是这个注解已经被弃用了,因此跳过这一步)。这的name不为空,进行分割,获取第一个,而后将值和class,设置到cacheActivateClass中,逐个将值设置到cacheName中,和传入做为参数的Map<String, Class<?>> extensionClasses = new HashMap<>();中。一路往回,回到loadExtensionClasses()方法中,这个方法将extetnsionClasses返回到getExtensionClasses()中
在这里插入图片描述
cachedClass是一个实例变量,返回的extensionClasses被设置回cachedClassess中,再把classes往上返回,getAdaptiveExtensionClass,此时cacheAdaptiveClass已经被设置了(有值的状况),直接返回,就获得了扩张的classes.
上面只完成了一半,还有一种状况是cacheAdaptiveClass执行完仍是,空的,那就要执行createAdaptiveExtensionClass()方法:
在这里插入图片描述在这里插入图片描述生成一个type.getSimpleName()$Adaptive的代理类,type里面的方法都被从新java

在这里插入图片描述这个是建立方法的类,若是方法没有包含adaptive注解,则直接不支持,抛出不支持的异常而后进行从新包装,其中对protol有特殊处理
在这里插入图片描述
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class)将生成的字符串用默认的编译(javassist)进行编译成class,赋值给cadaptiveClass类,以protocol为例根据代码生成字符串:
Protocol当生成extension的String
首先会判断有没有Adaptive,若是没有,则不进行生成代理类web

package com.alibaba.dubbo.rpc;          //type.package.getName()就是这是用原来的类的包名

import com.alibaba.dubbo.common.extension.ExtensionLoader; //这个是生成固定的引入

public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol{  //前一个是simpleName,后面实现用是全路径 

        //到这里,咱们已经生成了类名接下来就要开始处理方法了
        //遍历每个方法,只处理有Adaptive的方法

        //没有adaptive的方法
        public  int getDefaultPort(){
            throw new UnsupportedOperationException("method int getDefaultPort() of interface  com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
        }

        public void destroy(){
            throw new UnsupportedOperationException("method void destroy() of interface  com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
        }

        //包含adaptive的方法
        //首先校验参数里面有没有URL的参数
        //没有的话,校验参数类里面有没有包含URL的属性,若是属性里也没有URL这个变量,则会直接报错不会生成代理对象
        // throw new IllegalStateException("fail to create adaptive class for interface " + type.getName() + ": not found url parameter or url attribute in parameters of method " + method.getName());


        //没有URL参数的方法,可是有URL属性
        public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker invoker){
            
            if (arg0 == null) throw new IllegalArgumentException("invoker argument == null");
            if (arg0.getUrl() == null) throw new IllegalArgumentException("invoker argument getUrl() == null");
            com.alibaba.dubbo.common.URL url =  arg0.getUrl();

            //判断adaptive里有没有值,value,没有值则会生成默认的:protocol
            //判断接口参数里有没有 com.alibaba.dubbo.rpc.Invocation参数
            if (arg0 == null) throw new IllegalArgumentException("invocation == null");
            String methodName = arg0.getMethodName();

            //获取默认的cachedDefaultName
            //接下类根据就value来生成,若是只有一个value
            //由于是protocol因此生成
            String extName = ( url.getProtocol() == null ? "cachedDefaultName" : url.getProtocol() );
            //String extName =  url.getProtocol();
            if(extName == null)  throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(\" + url.toString() + \") use keys(protocol)\");
            com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        
            //若是返回类型不为空
            return extension.export(arg0);
        }

        public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class type,com.alibaba.dubbo.common.URL url){
            //参数里有URL参数的方法生成
            if (arg1 == null) throw new IllegalArgumentException("url == null");
            com.alibaba.dubbo.common.URL url = arg1;
            String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
            //String extName =  url.getProtocol();
            if(extName == null)  throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(\" + url.toString() + \") use keys(protocol)\");
            com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
            //若是返回类型不为空
            return extension.refer(arg0,arg1);
        }        
}

这里当要调用protocol的方法的时候,会根据生成的extName动态去调用,实现了动态代理的效果。所以可见,dubbo的export和refer的时候实际上是根据url中protocol来进行动态代理的,其余的方法也相似的。
接着来看下getExtension的方法,这里感受有回到了原点。若是name是ture的话,会使用默认的实例类,若是没有key为extName的类,则会再去加载对应的配置文件,那咱们来看下Protocol有哪些扩展类:redis

registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
memcached=memcom.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol

能够看到有这么多,在看下getExtension(name)的代码:spring

private T createExtension(String name) {
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && wrapperClasses.size() > 0) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                    type + ")  could not be instantiated: " + t.getMessage(), t);
        }
    }

这里前面基本能理解,从静态变量中获取cachedWrapperClasses中获取包装类,而后对instance进行包装。这里咱们能够看到,protocol的包装类有,filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper。因此对instance进行包装,若url.getProtocol是dubbo,最后获取到的是
instance = ProtocoleFilterWrapper(new ProtocolListenerWrapper(new DubboProtocoel()));
(cachedWrapperClasses是Set类型,因此包装类没有包装的顺序)
最后返回instance
好的,来分析剩下的部分。
至此就完成了获取adaptiveClass的方法。
有了class,调用newInstance进行实例化一个空壳实例,(返回到代码createAdaptiveExtension()中)有了空壳实例之后,在进行注入injectExtension((T) getAdaptiveExtensionClass().newInstance());
在这里插入图片描述
在这里,咱们终于看到了objectFactory的用法。objectFactory中只有一个方法getExtension(pt, property),用这个方法获取到对应的参数值,而后用反射将值注入,完成实例化,再放到相应的map里。至此,完成了整个过程,下次获取能够直接从map中根据key来获取了。
前面说了这么多,感受有点云里雾里,绕晕了,接下来,咱们用objectFactory的获取去来具体讲解下过程,这里不以源码为标准了。
首先在每一个ExtensionLoard实例里的ExtensionFactory为例开始讲解刘程:
先在调用getExtensionLoader,传入的type为ExtensionFactory.class,首先判断是否是接口,是否是包含了SPI注解。校验经过后,根据type到EXTENSION_LOADERS(是一个以type为key,extensionLoader为value的map,是个静态变量)里查找有没有已经生成好的ExtensionLoard,有的话直接返回,没有的话new一个,注意这里的构造函数,会生成一个objectFactory(ExtensionFactory),因为自己就是这个类,因此objectFactory为空。构建好ExtensionLoard之后,将这个值放入到EXTENSION_LOADERS 中,返回构建好的Extension。到这里getExtensionLoard(class)这一部分已经完成。
接下类getAdaptiveExtension(),来获取有效的实例
从cachedAdaptiveInstance中查看有没有实例,这个是一个实例变量,每一个 ExtensionLoard有本身的cachedAdaptiveInstance。因为这个是咱们刚刚new出来的,因此这个里面应该是没有值的。
createAdaptiveInstanceError判断这个有没有被实例化过且实例化失败过(这也是一个实例变量,用来记录实例化是否失败)没有失败,则进行实例化。因为是单例的,因此会锁住对象,再查一遍cachedAdaptiveInstance中有没有实例化(这个是单例模式经常使用的双重检锁机制,可是须要将锁变量定义成volatile或final,否则并发仍旧会有问题,涉及重排序,有兴趣能够本身去研究下),若是这个时候,仍是没有实例,那就要开始构建单例了,调用的方法是createAdaptiveExtension(),构建成功之后,就会将实例变量放到cachedAdaptiveInstance中,而后返回实例。
在createAdaptiveExtension中,须要调用getAdaptiveExtensionClass() 获取符合条件的Class,而后调用Class的newInstance获取到一个空壳的实例,再以此做为参数传给injectExtension(Object)进行属性的注入。
再来看下getAdaptiveExtensionClass中怎么来获取Class的
1.首先会getExtensionClasses()去获取,若是获取到,那cachedAdaptiveClass不为空,就将cachedAdaptiveClass返回。
咱们来看下getExtensionClassesses()是怎么作的,
1.1,检查下cachedClasses中有没有已经生成的classes(若是已经有了,cacheAdaptiveClass应该会会为空, 后面的代码解析中能够看到).这里又用了双重检锁。这里extensionFactory是首次建立的,因此cachedClasses确定是为空的,因此须要调用loadExtensionClasses方法来载入扩展文件。
在loadExtension中,首先获取SPI注解的value值,做为默认扩展类名(cacheDefaultExtensionName() 咱们看下ExtensionFactory里的@SPI注解,
在这里插入图片描述
因此没法设置cachedDefaultName,cachedDefaultName依旧为空。我继续往下走,接着就要开始加载loadDirectory,参数一个是map,存放取到的class,而后是文件夹,和全限名,因此要查看的路径是:
META-INF/dubbo/org.apache.dubbo.common.extension.ExtensionFactory
META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory
META-INF/services/org.apache.dubbo.common.extension.ExtensionFactory
META-INF/dubbo/com.alibaba.dubbo.common.extension.ExtensionFactory
META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory
META-INF/services/com.alibaba.dubbo.common.extension.ExtensionFactory
通常去加载这六个文件
在dubbo-common下找到META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory
在这里插入图片描述
在dubbo-spring-config下找到META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory
在这里插入图片描述
在dubbo-compatible下找到META-INF/services/com.alibaba.dubbo.common.extension.ExtensionFactory
在这里插入图片描述
既然找到了这些ExtensionFactory,那么,咱们先打开看下这些类,结果,你会发现,只有AdaptiveExtensionFactory有点不同,有@Adaptive注解
在这里插入图片描述
先看到这里,咱们来看接下来的操做具体加载类:
在loadDirectory方法中,经过ExtensionLoard的类加载器,或者系统类加载器,获取资源URL,而后经过loadResource方法来载入资源,进入loadResource的方法,将URL资源转成BufferedReader将里面的内容按行读取到系统中来,每一行数据进行切分,分红name和line两部分,line是一个全限名,因此直接用类加载器加载成Class,而后又调用loadClass进行加载。在loadClass中,首先进行判断获取到的Class是否是实现了type这个接口,若是没有,则报异常,在这里ExtensionFactory的全部扩展类都实现了ExtensionFactory这个接口,因此这一步校验都经过了。接下来,校验获取到的Class是否是有@Adaptive注解,有的话,直接赋值给cachedAdaptiveClass这个实例变量。在这里只有AdaptiveExtensionFactory有这个注解,因此ExtensionFactory的ExtensionLoard的cachedAdaptiveClass= AdaptiveExtensionFactory。因此AdaptiveExtensionFactory就到这里结束了。可是另外几个类是没有@Adaptive注解的,因此看下其余的判断,若是是包装类的话,要将当前class赋值给cacheWrapperClass。若是什么都不是,(剩下的扩展类都会走到这个分支里),首先获取下默认构造函数(就是无参构造fangfa),没有则会报异常(防止newInstance()失败),若是name为空,会去获取类上的注解@Extension(这个注解已经抛弃了)。咱们这里全部的类扩展文件都是有name的,因此不会去执行获取@Extension这一步。继续往下走,对传入的name进行切割成数组,默认去name[0].而后对cachedActivate进行赋值,若是class含有@Activate注解的,就将当前类以name[0]为key,class为value放到cachedActivates(Map)中。当前剩下的ExtensionFactory都没有扩展类,因此没有值能够放到cachedActivates中。接下来是一个name数组的遍历,将每个name作为key,value都是当前的Class,放到cacheNames这个map中,在将一样的值放到extensionClasses(这个是在loadExtensionClasses中定义,一直透传下来的),完成加载,返回到getExtensionClasses中,loadExtensionClasses里的extesionClassess被赋值给cachedClasses。再往上返回到getAdaptiveExtensionClass里,这时cachedAdaptiveClass已经不为空了,是AdaptiveExtensionFactory(这里直接用简单名字了,实际的是全限名),实例化在调用injectExtension,因为objectFactory是空,因此没有能够注入的,至此结束。
2.若是cachedAdaptiveClass仍是为空,就要createAdaptiveExtensionClass建立扩展类,这里就是前面用javassist生成一个动态代理类,能够看下前面protocol类生成的代码。可是这里cachedAdaptiveClass不为空,不用生成代理类。apache

接下来,咱们来看下执行了ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());各个变量的变化:
静态变量:
EXTENSION_LOADERS ---->ExtensionFactory.class = ExtensionLoard
实例变量:
Class<?> type ->ExtensionFactory.class
ExtensionFactory objectFactory ->null
ConcurrentMap<Class<?>, String> cachedNames
--------->org.apache.dubbo.common.extension.factory.SpiExtensionFactory.class = spi
---------> org.apache.dubbo.config.spring.extension.SpringExtensionFactory.class = spring
---------> org.apache.dubbo.common.extension.MyExtensionFactory.class = myfactory
Holder<Map<String, Class<?>>> cachedClasses
--------->spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory.class
---------> spring=org.apache.dubbo.config.spring.extension.SpringExtensionFactory.class
---------> myfactory=org.apache.dubbo.common.extension.MyExtensionFactory.class
Map<String, Object> cachedActivates 空
ConcurrentMap<String, Holder> cachedInstances 空
Holder cachedAdaptiveInstance -> AdaptiveExteionFactory实例对象
Class<?> cachedAdaptiveClass -> AdaptiveExteionFactory.class
String cachedDefaultName -> null
Throwable createAdaptiveInstanceError -> null
Set<Class<?>> cachedWrapperClasses -> null
Map<String, IllegalStateException> exceptions -> null数组

总结:
接下来咱们总结下dubbo的SPI机制的几种状况:
首先,能实现SPI机制的,都必须是接口,并且有@SPI注解,经过ExtensionLoard进行调用。
1.只有@SPI注解,注解中没有值,那默认实现也没有,可是接口的方法上会有@Adpative注解,这种当去获取AdaptiveExtension的时候,实际获取到的是一个javassist编译的代理来 类名$Adpative,通常会根据URL里面的参数,动态的实现功能逻辑
2.有@SPI(“xxxx”),这种通常获取代理类的时候,能获取到指定了xxxx的实现类
3.有@SPI,实现类中有@Adpative注解,这种当获取AdaptiveExtension的时候获取到的是有@Adpative注解的类。不会生成动态代理对象
4.有@SPI(“xxxx”),且实现类中有@Adpative,这种即有默认实现类,又有adaptiveExtension类。
到这里,完成了Dubbo的SPI机制,虽然只讲解了getAdaptiveExtension,可是其余相似,扩展方式相似,所谓触类旁通,就是这种,须要本身去理解剩下的。缓存