博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Dubbo的可扩展机制SPI源码解析
阅读量:2288 次
发布时间:2019-05-09

本文共 7833 字,大约阅读时间需要 26 分钟。

Demo

ExtensionLoader
extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);Protocol http = extensionLoader.getExtension("dubbo");System.out.println(http);

上面这个Demo就是Dubbo常见的写法,表示获取"dubbo"对应的Protocol扩展点。Protocol是一个接口。

 

在ExtensionLoader类的内部有一个static的ConcurrentHashMap,用来缓存某个接口类型所对应的ExtensionLoader实例

 

ExtensionLoader

ExtensionLoader表示一个扩展点加载器,在这个类中除开有上文的Map外,还有两个非常重要的属性:

  1. Class<?> type:表示当前ExtensionLoader实例是哪个接口的扩展点加载器
  2. ExtensionFactory objectFactory:表示当前ExtensionLoader实例的扩展点生成器(一个扩展点,就是一个接口的实现类的对象)

 

构造方法如下:

private ExtensionLoader(Class
type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());}

 

从上面的构造方法中可以看到一段比较特殊的代码:

ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()

 

在ExtensionLoader中有三个常用的方法:

  1. getExtension("dubbo"):表示获取名字为dubbo的扩展点实例
  2. getAdaptiveExtension():表示获取一个自适应的扩展点实例
  3. getActivateExtension(URL url, String[] values, String group):表示一个可以被url激活的扩展点实例,后文详细解释

 

其中,什么是自适应的扩展点实例?它其实就是当前这个接口类型的一个代理类,可以通过这个代理类去获取某个名字的扩展点。那为什么要这么设计呢?

 

这是因为对于某个接口的实现类实例,并不是仅仅只能通过Dubbo框架来生成,比如通过getExtension("dubbo")方法所获得扩展点实例,是由Dubbo框架根据名字对应的实现类帮我们生成的一个实例对象。而有的时候,我们需要从Dubbo之外去获取实例对象,比如从Spring容器中根据名字取获取bean,来作为一个扩展点实例。

 

所以ExtensionFactory表示一个扩展点工厂,在Dubbo中有三个实现类:

  1. AdaptiveExtensionFactory:负责从SpiExtensionFactory或SpringExtensionFactory中得到扩展点实例对象
  2. SpiExtensionFactory:利用Dubbo的Spi机制获取一个扩展点实例
  3. SpringExtensionFactory:从Spring的ApplicationContext中获取bean作为一个扩展点实例

 

所以回到上文的那么代码,它拿到的就是一个AdaptiveExtensionFactory实例, objectFactory表示一个扩展点实例工厂。

 

getExtension(String name)方法

在调用getExtension去获取一个扩展点实例后,会对实例进行缓存,下次再获取同样名字的扩展点实例时就会从缓存中拿了。

 

createExtension(String name)方法

在调用createExtension(String name)方法去创建一个扩展点实例时,要经过以下几个步骤:

  1. 根据name找到对应的扩展点实现类
  2. 根据实现类生成一个实例,把实现类和对应生成的实例进行缓存
  3. 对生成出来的实例进行依赖注入(给实例的属性进行赋值)
  4. 对依赖注入后的实例进行AOP(Wrapper),把当前接口类的所有的Wrapper全部一层一层包裹在实例对象上,没包裹个Wrapper后,也会对Wrapper对象进行依赖注入
  5. 返回最终的Wrapper对象

 

getExtensionClasses

getExtensionClasses()是用来加载当前接口所有的扩展点实现类的,返回一个Map。之后可以从这个Map中按照指定的name获取对应的扩展点实现类。

 

当把当前接口的所有扩展点实现类都加载出来后也会进行缓存,下次需要加载时直接拿缓存中的。

 

Dubbo在加载一个接口的扩展点时,思路是这样的:

  1. 根据接口的全限定名去META-INF/dubbo/internal/目录下寻找对应的文件,调用loadResource方法进行加载
  2. 根据接口的全限定名去META-INF/dubbo/目录下寻找对应的文件,调用loadResource方法进行加载
  3. 根据接口的全限定名去META-INF/services/目录下寻找对应的文件,调用loadResource方法进行加载

 

这里其实会设计到老版本兼容的逻辑,不解释了。

 

loadResource方法

 

loadResource方法就是完成对文件内容的解析,按行进行解析,会解析出"="两边的内容,"="左边的内容就是扩展点的name,右边的内容就是扩展点实现类,并且会利用ExtensionLoader类的类加载器来加载扩展点实现类。

 

然后调用loadClass方法对name和扩展点实例进行详细的解析,并且最终把他们放到Map中去。

 

loadClass方法

loadClass方法会做如下几件事情:

  1. 当前扩展点实现类上是否存在@Adaptive注解,如果存在则把该类认为是当前接口的默认自适应类(接口代理类),并把该类存到cachedAdaptiveClass属性上。
  2. 当前扩展点实现是否是一个当前接口的一个Wrapper类,如果判断的?就是看当前类中是否存在一个构造方法,该构造方法只有一个参数,参数类型为接口类型,如果存在这一的构造方法,那么这个类就是该接口的Wrapper类,如果是,则把该类添加到cachedWrapperClasses中去, cachedWrapperClasses是一个set。
  3. 如果不是自适应类,或者也不是Wrapper类,则判断是有存在name,如果没有name,则报错。
  4. 如果有多个name,则判断一下当前扩展点实现类上是否存在@Activate注解,如果存在,则把该类添加到cachedActivates中,cachedWrapperClasses是一个map
  5. 最后,遍历多个name,把每个name和对应的实现类存到extensionClasses中去,extensionClasses就是上文所提到的map。

 

至此,加载类就走完了。

 

回到createExtension(String name)方法中的逻辑,当前这个接口的所有扩展点实现类都扫描完了之后,就可以根据用户所指定的名字,找到对应的实现类了,然后进行实例化,然后进行IOC(依赖注入)和AOP。

 

Dubbo中的IOC

  1. 根据当前实例的类,找到这个类中的setter方法,进行依赖注入
  2. 先分析出setter方法的参数类型pt
  3. 在截取出setter方法所对应的属性名property
  4. 调用objectFactory.getExtension(pt, property)得到一个对象,这里就会从Spring容器或通过DubboSpi机制得到一个对象,比较特殊的是,如果是通过DubboSpi机制得到的对象,是pt这个类型的一个自适应对象。
  5. 再反射调用setter方法进行注入

 

Dubbo中的AOP

dubbo中也实现了一套非常简单的AOP,就是利用Wrapper,如果一个接口的扩展点中包含了多个Wrapper类,那么在实例化完某个扩展点后,就会利用这些Wrapper类对这个实例进行包裹,比如:现在有一个DubboProtocol的实例,同时对于Protocol这个接口还有很多的Wrapper,比如ProtocolFilterWrapper、ProtocolListenerWrapper,那么,当对DubboProtocol的实例完成了IOC之后,就会先调用new ProtocolFilterWrapper(DubboProtocol实例)生成一个新的Protocol的实例,再对此实例进行IOC,完了之后,会再调用new ProtocolListenerWrapper(ProtocolFilterWrapper实例)生成一个新的Protocol的实例,然后进行IOC,从而完成DubboProtocol实例的AOP。

 

AdaptiveClass

上文多次提到了Adaptive,表示一个接口的自适应类,这里详细的来讲讲。

 

通过getAdaptiveExtension方法获得的实例就是Adaptive类的实例,在Dubbo中有两种方式来针对某个接口得到一个Adaptive类,一种是在某个接口的实现类上指定一个@Adaptive注解,那么该类就是这个接口的Adaptive类,或者利用Dubbo的默认实现来得到一个Adaptive类,一个接口只能有一个Adaptive类。

 

如果是手动实现的Adaptive类,那么自适应逻辑就是自己实现的。如果是有Dubbo默认实现的,那么我们就看看Dubbo是如何实现Adaptive类的。

 

createAdaptiveExtensionClass方法

createAdaptiveExtensionClass方法就是Dubbo中默认生成Adaptive类实例的逻辑。说白了,这个实例就是当前这个接口的一个代理对象。比如下面的代码:

ExtensionLoader
extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);Protocol protocol = extensionLoader.getAdaptiveExtension();

这个代码就是Protocol接口的一个代理对象,那么代理逻辑就是在new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate()方法中。

 

  1. type就是接口
  2. cacheDefaultName就是该接口默认的扩展点实现的名字

 

看个例子,Protocol接口的Adaptive类:

package org.apache.dubbo.rpc;import org.apache.dubbo.common.extension.ExtensionLoader;public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {        public void destroy()  {        throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");    }    public int getDefaultPort()  {        throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");    }        public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {        if (arg0 == null)             throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");        if (arg0.getUrl() == null)             throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");                org.apache.dubbo.common.URL url = arg0.getUrl();                String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );        if(extName == null)             throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");                org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);                return extension.export(arg0);    }    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {        if (arg1 == null) throw new IllegalArgumentException("url == null");        org.apache.dubbo.common.URL url = arg1;        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);        return extension.refer(arg0, arg1);    }}

 

可以看到,Protocol接口中有四个方法,但是只有export和refer两个方法进行代理。为什么?因为Protocol接口中在export方法和refer方法上加了@Adaptive注解。但是,不是只要在方法上加了@Adaptive注解就可以进行代理,还有其他条件,比如:

  1. 该方法如果是无参的,那么则会报错
  2. 该方法有参数,可以有多个,并且其中某个参数类型是URL,那么则可以进行代理
  3. 该方法有参数,可以有多个,但是没有URL类型的参数,那么则不能进行代理
  4. 该方法有参数,可以有多个,没有URL类型的参数,但是如果这些参数类型,对应的类中存在getUrl方法(返回值类型为URL),那么也可以进行代理

 

所以,可以发现,某个接口的Adaptive对象,在调用某个方法时,是通过该方法中的URL参数,通过调用ExtensionLoader.getExtensionLoader(com.luban.Car.class).getExtension(extName);得到一个扩展点实例,然后调用该实例对应的方法。

 

Activate扩展点

上文说到,每个扩展点都有一个name,通过这个name可以获得该name对应的扩展点实例,但是有的场景下,希望一次性获得多个扩展点实例,可以通过传入多个name来获取,可以通过识别URL上的信息来获取:

 

extensionLoader.getActivateExtension(url, new String[]{"car1", "car2"}); 这个可以拿到name为car1和car2的扩展类实例,同时还会通过传入的url寻找可用的扩展类,  怎么找的呢?

 

在一个扩展点类上,可以添加@Activate注解,这个注解的属性有:

  1. String[] group():表示这个扩展点是属于拿组的,这里组通常分为PROVIDER和CONSUMER,表示该扩展点能在服务提供者端,或者消费端使用
  2. String[] value():指示的是URL中的某个参数key,当利用getActivateExtension方法来寻找扩展点时,如果传入的url中包含的参数的所有key中,包括了当前扩展点中的value值,那么则表示当前url可以使用该扩展点。

转载地址:http://udbnb.baihongyu.com/

你可能感兴趣的文章
从0到1,决战Spring Boot《Spring Boot 2实战之旅》
查看>>
5面终于拿到字节跳动offer!忍不住和大家分享一波
查看>>
拿到阿里、字节offer后。我总结了一线大厂Java面试重难点:Java基础+并发+JVM+算法+框架+分布式+架构设计
查看>>
金九银十已过 成功入职美团,面试回顾及个人总结:算法+框架+Redis+分布式+JVM
查看>>
香!阿里P8手写3份满级“并发编程”笔记,原理→精通→实战
查看>>
五面美团后,我总结出美团面试四大难题:JVM+微服务+MySQL+Redis
查看>>
滴滴Java后台3面题目:网络+内存溢出+各种锁+高性能+消息队列
查看>>
大厂面试果然名不虚传,蚂蚁三面凉经,真的是“太难了”
查看>>
分享一次止于三面的阿里面试之旅,是我不配呀
查看>>
美团工作7年,精华全在这份学习笔记里了,已成功帮助多位朋友拿到5个大厂Offer
查看>>
淘宝架构师又出神作,Java异步编程实战笔记总结,彻底被征服
查看>>
深入OAuth2核心源码,阿里大佬的Spring Security手册惊呆我了
查看>>
普本毕业,阿里五面成功斩下offer,名校出身的我究竟输在哪?
查看>>
最新“美团+字节+腾讯”三面面经,你能撑到哪一面?
查看>>
三年Java开发,年底跳槽挂了阿里和字节,却收获美团offer,分享大厂面试心得
查看>>
4面全过之后还有交叉面,阿里面试也太“刺激”了
查看>>
手慢无!出自美团内部的精通Java并发编程手册,打倒高并发
查看>>
一线互联网大厂面经分享:阿里三面+头条四面+腾讯二面+美团四面
查看>>
快手三面(Java岗),意向已拿,盘点一下面试官都问了些啥?
查看>>
“刚刚”顺丰校招二面+美团社招三面面经,分享给准备冲刺金三银四的你
查看>>