欢迎加入 DUBBO交流群:259566260
以前不少人问我Dubbo插件化是怎么实现的,我都是简单回答SPI。了解SPI的人知道,它只是提供一种协议,并无提供相关插件化实施的接口,不像OSGI那样有一成套实施插件化API。它只是规定在META-INF目录下提供接口的实现描述文件,框架自己定义接口、规范,第三方只须要将本身实如今META-INF下描述清楚,那么框架就会自动加载你的实现,至于怎么加载,JDK并无提供相关API,而是框架设计者须要考虑和实现的,而且在META-INF下面对实现描述规则,也是须要框架设计者来规定。好比Dubbo的规则是在META-INF/dubbo、META-INF/dubbo/internal或者META-INF/services下面以须要实现的接口全面去建立一个文件,而且在文件中以properties规则同样配置实现类的全面以及分配实现一个名称。图23是对Cluster接口扩展实现的描述 java
上面对Dubbo的插件实现方式和规则进行了简单介绍,以及提到SPI是一种插件化的规范,并无提供实施的API,是须要框架本身去实现的。Dubbo对这一块的实现所有都集中在类ExtensionLoader中,那么接下来将围绕这个类来介绍Dubbo插件化的实现,在介绍Dubbo插件化实施以前,须要知道Dubbo框架是以URL为总线的模式,即运行过程当中全部的状态数据信息均可以经过URL来获取,好比当前系统采用什么序列化,采用什么通讯,采用什么负载均衡等信息,都是经过URL的参数来呈现的,因此在框架运行过程当中,运行到某个阶段须要相应的数据,均可以经过对应的Key从URL的参数列表中获取,好比在cluster模块,到服务调用触发到该模块,则会从URL中获取当前调用服务的负载均衡策略,以及mock信息等。 负载均衡
ExtensionLoader是一个单例工厂类,它对外暴露getExtensionLoader静态方法返回一个ExtensionLoader实体,这个方法的入参是一个Class类型,这个方法的意思是返回某个接口的ExtensionLoader。那么对于某一个接口,只会有一个ExtensionLoader实体。ExtensionLoader实体对外暴露了图24中的一些接口来获取扩展实现。 框架
上图的方法归为几类,分别是activate extension、adaptive extension、default extension、get extension by name以及supported extension。经过图24发现activate extension都须要传入url参数,这里涉及到Activate注解,这个注解主要用处是标注在插件接口实现类上,用来配置该扩展实现类激活条件。在Dubbo框架里面的Filter的各类实现类都经过Activate标注,用来描述该Filter何时生效。好比MonitorFilter经过Activate标注用来告诉Dubbo框架这个Filter是在服务提供端和消费端会生效的;而TimeoutFilter则是只在服务提供端生效,消费端是不会调用该Filter;ValidationFilter要激活的条件除了在消费端和服务提供端激活,它还配置了value,这个表述另外一个激活条件,上面介绍要获取activate extension都须要传入URL对象,那么这个value配置的值则表述URL必须有指定的参数才能够激活这个扩展。例如ValidationFilter则表示URL中必须包含参数validation(Constants.VALIDATION_KEY常量的值就是validation),不然即便是消费端和服务端都不会激活这个扩展实现,仔细的同窗还会发如今ValidationFilter中的Activate注解还有一个参数order,这是表示一种排序规则。由于一个接口的实现有多种,返回的结果是一个列表,若是不指定排序规则,那么可能列表的排序不可控,为了实现这个因此添加了order属性用来控制排序,其中order的值越大,那么该扩展实现排序就越靠前。除了经过order来控制排序,还有before和after来配置当前扩展的位置,before和after配置的值是扩展的别名(扩展实现的别名是在图23中等号左边内容,下面出现的别名均是此内容)。 ide
@Activate(group = {Constants.PROVIDER, Constants.CONSUMER}) public class MonitorFilter implements Filter {……} @Activate(group = Constants.PROVIDER) public class TimeoutFilter implements Filter {……} @Activate(group = { Constants.CONSUMER, Constants.PROVIDER }, value = Constants.VALIDATION_KEY, order = 10000) public class ValidationFilter implements Filter {……}
上面基本对activate介绍的差很少了,在Dubbo框架中对这个用的最多的就是Filter的各类实现,由于Dubbo的调用会通过一个过滤器链,哪些Filter这个链中是经过各类Filter实现类的Activate注解来控制的。包括上面说的排序,也能够理解为过滤器链中各个Filter的先后顺序。这里的顺序须要注意一个地方,这里的排序均是框架自己实现扩展的进行排序,用户自定义的扩展默认是追加在列表后面。说到这里具体例子: 测试
<dubbo:reference id=”fooRef” interface=”com.foo.Foo” ….. filter=”A,B,C”/> 编码
假设上面是一个有效的消费端服务引用,其中配置了一个filter属性,而且经过逗号隔开配置了三个过滤器A,B,C(A,B,C均为Filter实现的别名),那么对于接口Foo调用的过滤器链是怎么样的呢?首先Dubbo会加载默认的过滤器(通常消费端有三个ConsumerContextFilter,MonitorFilter,FutureFilter),而且对这些默认的过滤器实现进行排序(ActivateComparator实现排序逻辑),这写默认过滤器实现会在过滤器链前面,后面紧接着的才是A,B,C三个自定义过滤器。 url
上面介绍了activate extension,下面介绍ExtensionLoader另外一个重要模块adaptive extension。Dubbo框架提供的各类接口均有不少种类的实现,在引用具体实现的时候不可能经过硬编码制定引用哪一个实现,这样整个框架的灵活性严重下降。因此为了可以适配一个接口的各类实现,便有了adaptive extension这一说。对一个接口实现的适配器Dubbo提供两种途径,第一种途径是对某个接口实现对应的适配器,第二种是Dubbo框架动态生成适配器类。先对第一种途径进行介绍,这种途径也最好理解,对于这种途径Dubbo也提供了一个注解—Adaptive,他用来标注在接口的某个实现上,表示这个实现并非提供具体业务支持,而是做为该接口的适配器。对于这种途径的使用在Dubbo框架中ExtensionFactory的实现类AdaptiveExtensionFactory就是实现适配的功能,它的类被Adaptive进行了标注,那么当调用ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()的时候将会返回AdaptiveExtensionFactory实体,用来适配ExtensionFactory接口的SPIExtensionFactory和SpringExtensionFactory两种实现,在AdaptiveExtensionFactory将会根据运行时的状态来肯定具体调用ExtensionFactory的哪一个实现。 spa
而第二种相对于第一种来讲就隐晦一点,是ExtensionLoader经过分析接口配置的adaptive规则动态生成adaptive类而且加载到ClassLoader中,来实现动态适配。配置adaptive的规则也是经过Adaptive注解来设置,该注解有一个value属性,经过设置这个属性即可以设置该接口的Adaptive的规则,上面说过服务调用的全部数据都可以从URL获取(Dubbo的URL总线模式),那么须要Dubbo帮咱们动态生成adaptive的扩展接口的方法入参必须包含URL,这样才能根据运行状态动态选择具体实现。这里列举一下Transporter接口中配置adaptive规则。 .net
@SPI("netty") public interface Transporter { /** * Bind a server. * * @see com.alibaba.dubbo.remoting.Transporters#bind(URL, Receiver, ChannelHandler) * @param url server url * @param handler * @return server * @throws RemotingException */ @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY}) Server bind(URL url, ChannelHandler handler) throws RemotingException; /** * Connect to a server. * * @see com.alibaba.dubbo.remoting.Transporters#connect(URL, Receiver, ChannelListener) * @param url server url * @param handler * @return client * @throws RemotingException */ @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY}) Client connect(URL url, ChannelHandler handler) throws RemotingException; }
Transporter接口提供了两个方法,一个是connect(用来建立客户端链接),另外一个是bind(用来绑定服务端端口提供服务),而且这两个方法上面均经过Adaptive注解配置了value属性,bind配置的是server和transporter,connect配置的是client和transporter。那么配置这些值有什么用呢?下面看看ExtensionLoader根据这些生成了什么样的adaptive代码。 插件
package com.alibaba.dubbo.remoting; import com.alibaba.dubbo.common.extension.ExtensionLoader; public class Transporter$Adpative implements com.alibaba.dubbo.remoting.Transporter{ public com.alibaba.dubbo.remoting.Client connect( com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException { if (arg0 == null) throw new IllegalArgumentException("url == null"); com.alibaba.dubbo.common.URL url = arg0; String extName = url.getParameter("client", url.getParameter("transporter", "netty")); if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])"); com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName); return extension.connect(arg0, arg1); } public com.alibaba.dubbo.remoting.Server bind( com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException { if (arg0 == null) throw new IllegalArgumentException("url == null"); com.alibaba.dubbo.common.URL url = arg0; String extName = url.getParameter("server", url.getParameter("transporter", "netty")); if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])"); com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName); return extension.bind(arg0, arg1); } }
上面是ExtensionLoader自动生成的Transporter$Adpative类,而且实现了Transporter接口,下面咱们分别看看在它connect和bind中分别作了哪些逻辑。先看看bind方法代码段:
public com.alibaba.dubbo.remoting.Server bind( com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException { if (arg0 == null) throw new IllegalArgumentException("url == null"); com.alibaba.dubbo.common.URL url = arg0; String extName = url.getParameter("server", url.getParameter("transporter", "netty")); if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])"); com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName); return extension.bind(arg0, arg1); }
能够看到bind方法先对url参数(arg0)进行了非空判断,而后即是调用url.getParameter方法,首先是获取server参数,若是没有边获取transporter参数,最后若是两个参数均没有,extName即是netty。获取完参数以后,紧接着对extName进行非空判断,接下来即是获取Transporter的ExtensionLoader,而且获取别名为extName的Transporter实现,并调用对应的bind,进行绑定服务端口操做。connect也是相似,只是它首先是从url中获取client参数,在获取transporter参数,一样若是最后两个参数都没有,那么extName也是netty,也依据extName获取对已的接口扩展实现,调用connect方法。
到这里或许你已经明白了ExtensionLoader是怎么动态生成adaptive,上面从url中获取server,client仍是transporter参数均是在Transporter接口的方法经过Adaptive注解配置的value属性。其中netty是经过注解SPI制定当前接口的一种默认实现。这即是Dubbo经过ExtensionLoader动态生成adaptive类来动态适配接口的全部实现。
上面对activate和adaptive进行了详细的介绍,这两部分对已ExtensionLoader的实现分别是方法getActivateExtension(URL url, String[] values, String group) 和createAdaptiveExtensionClassCode(),若是感兴趣,能够去查看ExtensionLoader源码。接下来对get extension by name和default extension介绍一下,get extension by name这个没什么好介绍的,就是经过接口实现的别名来获取某一个具体的服务。而default extension须要坐一下详细介绍,Dubbo的SPI规范除了上面说的在制定文件夹下面描述服务的实现信息以外,在被实现的接口必须标注SPI注解,用来告诉Dubbo这个接口是经过SPI来进行扩展实现的,不然ExtensionLoader则不会对这个接口建立ExtensionLoader实体,而且调用ExtensionLoader.getExtensionLoader方法会出现IllegalArgumentException异常。那说这些和默认扩展实现有什么关系呢?在接口上标注SPI注解的时候能够配置一个value属性用来描述这个接口的默认实现别名,例如上面Transporter的@SPI(“netty”)就是指定Transporter默认实现是NettyTransporter,由于NettyTransporter的别名是netty。这里再对服务别名补充有点,别名是站在某一个接口的维度来区分不一样实现的,因此一个接口的实现不能有相同的别名,不然Dubbo框架将启动失败,固然不一样接口的各自实现别名能够相同。到此ExtensionLoader的实现原则和基本原理介绍完了,接下来咱们来看看怎么基于Dubbo的ExtensionLoader来实施咱们本身的插件化。一样仍是dubbo-demo项目中进行演示,在其中建立了一个demo-extension模块。
插件化的第一步是抽象一个接口,从定义了插件的规范,那么咱们先建立一个MyFirstExtension接口,而且标注了SPI注解,同时制定默认实现是别名为default的扩展实现。
@SPI("default") public interface MyFirstExtension { public String sayHello(String name,ExtensionType type); }
@Adaptive public class AdaptiveExtension implements MyFirstExtension { @Override public String sayHello(String name,ExtensionType type) { ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(MyFirst Extension.class); MyFirstExtension extension= (MyFirstExtension) extensionLoader.getDefaultExtension(); switch (type){ case DEFAULT: extension= (MyFirstExtension) extensionLoader.getExtension("default"); break; case OTHER: extension= (MyFirstExtension) extensionLoader.getExtension("other"); break; } return extension.sayHello(name,type); } }
到此插件的定义以及插件的实现都已经完毕,下面咱们来验证一下是否成功依托Dubbo的插件机制管理了咱们的插件。我建立了一个测试类ExtensionTest来验证,运行该测试类便会等到图27所示的结果,若是调整ExtensionType便会获得不一样的记过,说明咱们的插件已经成功整合到了Dubbo框架里面。
public class ExtensionTest { public static void main(String[] args){ ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(MyFirstExtension.class); MyFirstExtension myFirstExtension = (MyFirstExtension) extensionLoader.getAdaptiveExtension(); System.out.println(myFirstExtension.sayHello("bieber",ExtensionType.DEFAULT)); } }
到此关于Dubbo插件化的内容介绍完了,其实能够把ExtensionLoader看成是Spring的IOC容器,只不过IOC容器里面作的事情是帮咱们初始化和管理bean,咱们能够根据咱们须要的bean类型或者bean的id来获取对应的bean实体,而Dubbo里面的ExtensionLoader岂不是同样,只不过它管理的是插件,一样咱们能够根据具体插件实现别名和插件接口来获取咱们想要的插件实现。另外一个不一样点是Spring是经过XML的方式告诉Spring个人bean的实现类全路径,而Dubbo则是经过SPI的方式告诉ExtensionLoader具体实现类信息。若是你理解了这个,那么你就理解ExtensionLoader了。