ExtensionLoader可以与Spring IOC容器进行类比:
IOC容器:进行初始化和管理bean,用户可以根据需要的bean类型或bean的ID来获取对应的bean实体,通过XML的方式告诉Spring的bean实现类全路径;
ExtensionLoader:进行dubbo中插件的管理,用户可以根据具体插件实现别名和插件插口来获取想要的插件实现,通过SPI的方式告诉ExtensionLoader具体实现类信息。
以下内容为转载内容,原文地址:http://bbs.dubboclub.net/read-32.html
之前很多人问我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接口扩展实现的描述
图片:QQ图片20150523205010.png
ExtensionLoader是一个单例工厂类,它对外暴露getExtensionLoader静态方法返回一个ExtensionLoader实体,这个方法的入参是一个Class类型,这个方法的意思是返回某个接口的ExtensionLoader。那么对于某一个接口,只会有一个ExtensionLoader实体。ExtensionLoader实体对外暴露了图24中的一些接口来获取扩展实现。
图片:QQ图片20150523205101.png
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中等号左边内容,下面出现的别名均是此内容)。
复制代码
1
2
3
4
5
6
|
@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 {……}
|
extension。Dubbo框架提供的各种接口均有很多种类的实现,在引用具体实现的时候不可能通过硬编码制定引用哪个实现,这样整个框架的灵活性严重降低。所以为了能够适配一个接口的各种实现,便有了adaptive extension这一说。对一个接口实现的适配器Dubbo提供两种途径,第一种途径是对某个接口实现对应的适配器,第二种是Dubbo框架动态生成适配器类。先对第一种途径进行介绍,这种途径也最好理解,对于这种途径Dubbo也提供了一个注解—Adaptive,他用来标注在接口的某个实现上,表示这个实现并不是提供具体业务支持,而是作为该接口的适配器。对于这种途径的使用在Dubbo框架中ExtensionFactory的实现类AdaptiveExtensionFactory就是实现适配的功能,它的类被Adaptive进行了标注,那么当调用ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()的时候将会返回AdaptiveExtensionFactory实体,用来适配ExtensionFactory接口的SPIExtensionFactory和SpringExtensionFactory两种实现,在AdaptiveExtensionFactory将会根据运行时的状态来确定具体调用ExtensionFactory的哪个实现。
而第二种相对于第一种来说就隐晦一点,是ExtensionLoader通过分析接口配置的adaptive规则动态生成adaptive类并且加载到ClassLoader中,来实现动态适配。配置adaptive的规则也是通过Adaptive注解来设置,该注解有一个value属性,通过设置这个属性便可以设置该接口的Adaptive的规则,上面说过服务调用的所有数据均可以从URL获取(Dubbo的URL总线模式),那么需要Dubbo帮我们动态生成adaptive的扩展接口的方法入参必须包含URL,这样才能根据运行状态动态选择具体实现。这里列举一下Transporter接口中配置adaptive规则。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@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;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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);
}
|
上面对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的扩展实现。
复制代码
1
2
3
4
|
@SPI
(
"default"
)
public
interface
MyFirstExtension {
public
String sayHello(String name,ExtensionType type);
}
|
那么接下来就是对这个插件接口提供不同的实现,可以看到我上面接口方法sayHello方法入参中并没有URL类型,所以不能通过Dubbo动态给我生成adaptive类,需要我自己来实现一个适配类。我的适配类如下:
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@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);
}
}
|
可见在AdaptiveExtension中将会根据ExtensionType分发扩展的具体实现,并触发sayHello方法。我在demo-extension模块中对MyFirstExtension接口提供了两种实现,它们如图25所示,并且在META-INF/dubbo下面创建了文件com.bieber.dubbo.extension.MyFirstExtension其中内容如图26所示。
图片:QQ图片20150523205346.png
图片:QQ图片20150523205414.png
到此插件的定义以及插件的实现都已经完毕,下面我们来验证一下是否成功依托Dubbo的插件机制管理了我们的插件。我创建了一个测试类ExtensionTest来验证,运行该测试类便会等到图27所示的结果,如果调整ExtensionType便会得到不同的记过,说明我们的插件已经成功整合到了Dubbo框架里面。
1
2
3
4
5
6
|
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));
}
}
|
图片:QQ图片20150523205502.png
到此关于Dubbo插件化的内容介绍完了,其实可以把ExtensionLoader当作是Spring的IOC容器,只不过IOC容器里面做的事情是帮我们初始化和管理bean,我们可以根据我们需要的bean类型或者bean的id来获取对应的bean实体,而Dubbo里面的ExtensionLoader岂不是一样,只不过它管理的是插件,同样我们可以根据具体插件实现别名和插件接口来获取我们想要的插件实现。另一个不同点是Spring是通过XML的方式告诉Spring我的bean的实现类全路径,而Dubbo则是通过SPI的方式告诉ExtensionLoader具体实现类信息。如果你理解了这个,那么你就理解ExtensionLoader了。