一步步解析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里面的方法都被重新

在这里插入图片描述这个是创建方法的类,如果方法没有包含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,如果没有,则不进行生成代理类

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有哪些扩展类:

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)的代码:

 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不为空,不用生成代理类。

接下来,我们来看下执行了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,但是其他类似,扩展方式类似,所谓举一反三,就是这种,需要自己去理解剩下的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值