因 dubbo 框架是建立的 SPI 机制上,因此在探寻 dubbo 框架源码前,我们需要先把 SPI 机制了解透彻
SPI 全称 Service Provider Interface,是 Java 提供的一套用来被第三方实现或者扩展的 API,它可以用来启用框架扩展和替换组件。
可以看到,SPI 的本质,其实是帮助程序,为某个特定的接口寻找它的实现类。而且哪些实现类的会加载,是个动态过程(不是提前预定好的)。有点类似 IOC 的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以 SPI 的核心思想就是解耦。
要使用 Java SPI,需要遵循如下约定:
示例
先定义一个接口
public interface InfoService {
Object sayHello(String name) ;
}
在定义几个实现类
public class InfoServiceAImpl implements InfoService {
@Override
public Object sayHello(String name) {
System.out.println(name+",你好,调通了 A 实现!");
return name+",你好,调通了 A 实现!";
}
}
public class InfoServiceBImpl implements InfoService {
@Override
public Object sayHello(String name) {
System.out.println(name+",你好,调通了 B 实现!");
return name+",你好,调通了 B 实现!";
}
}
ServiceLoader<InfoService> servicesLoader = ServiceLoader.load(InfoService.class);
for (InfoService infoService : servicesLoader) {
infoService.sayHello("hello");
}
程序遍历运行时,可看到只加载配置文件中指定的实现类
至此,整个 java SPI 的机制使用介绍完毕。
核心功能类
java 之所以能够顺利根据配置加载这个实现类,完全依赖于jdk 内的一个核心类:
在上一节中,可以看到,java spi 机制非常简单,就是读取指定的配置文件,将所有的类都加载到程序中。而这种机制,存在很多缺陷,比如:
与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们就可以按需加载指定的实现类。另外,需要在接口上标注 @SPI 注解。表明此接口是 SPI 的扩展点:
测试代码
ExtensionLoader<InfoService> extensionLoader = ExtensionLoader.getExtensionLoader(InfoService.class);
InfoService defaultExtension = extensionLoader.getDefaultExtension();
defaultExtension.sayHello("default");
InfoService a = extensionLoader.getExtension("a");
a.sayHello("zls");
InfoService b = extensionLoader.getExtension("b");
b.sayHello("zwq");
Dubbo 的 Spi 机制虽然对原生 SPI 有了增强,但功能还远远不够。在工作中,某种时候存在这样的情形,需要同时启用某个接口的多个实现类,如 Filter 过滤器。我们希望某种条件下启用这一批实现,而另一种情况下启用那一批实现,比如:希望的 RPC 调用的消费端和服务端,分别启用不同的两批 Filter,怎么处理呢?
这时我们的条件激活注解@Activate,就派上了用场
Activate 注解表示一个扩展是否被激活(使用),可以放在类定义和方法上,dubbo 用它在 spi 扩展类定义上,表示这个扩展实现激活条件和时机。它有两个设置过滤条件的字段,group,value 都是字符数组。 用来指定这个扩展类在什么条件下激活。
下面以 com.alibaba.dubbo.rpc.filter 接口的几个扩展来说明。
@Activate(group = "zls",order = 1)
public class FilterA implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
System.out.println("您好调通了filterA实现");
return null;
}
}
表示如果过滤器使用方(通过 group 指定)属于 zls 时就激活使用这个过滤器
@Activate(group = "james",order = 2,value = "groupId")
public class FilterB implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
System.out.println("您好调通了filterB实现");
return null;
}
}
表示如果过滤器使用方(通过 group 指定)属于james并且 URL 中有参数 groupId时就激活使用这个过滤器
创建文件
META-INF/dubbo/com.alibaba.dubbo.rpc.Filter
a=com.enjoy.filter.FilterA
b=com.enjoy.filter.FilterB
c=com.enjoy.filter.FilterC
测试代码如下:
ExtensionLoader<Filter> extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class);
//随便写
URL url = URL.valueOf("test://localhost/test");
url = url.addParameter("groupId","aaa");
List<Filter> filterList = extensionLoader.getActivateExtension(url, "", "james");
for (Filter filter : filterList) {
filter.invoke(null,null);
}
下面这段代码表示: 添加c过滤器
ExtensionLoader<Filter> extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class);
URL url = URL.valueOf("test://localhost/test");
url = url.addParameter("groupId","aaa");
url = url.addParameter("myfilter","c");
List<Filter> filterList = extensionLoader.getActivateExtension(url, "myfilter", "james");
for (Filter filter : filterList) {
filter.invoke(null,null);
}
如果要去除过滤器
url = url.addParameter(“myfilter”,"-a");
添加c过滤器,删除a过滤器
url = url.addParameter(“myfilter”,"-a,c");
我们在前面演示了 dubbo SPI 的使用,但是有一个问题,扩展点对应的实现类不能在程序运行时动态指定,就是 extensionLoader.getExtension 方法写死了扩展点对应的实现类,不能在程序运行期间根据运行时参数进行动态改变。
而我们希望在程序使用时,对实现类进行懒加载,并且能根据运行时情况来决定,应该启用哪个扩展类。为了解决这个问题,dubbo 引入了 Adaptive注解,也就是 dubbo 的自适应机制。
在方法上添加@Adaptive注解
@SPI("b")
public interface InfoService {
Object sayHello(String name) ;
@Adaptive
Object passInfo(String name, URL url) ;
}
测试代码
ExtensionLoader<InfoService> loader =
ExtensionLoader.getExtensionLoader(InfoService.class);
InfoService adaptiveExtension = loader.getAdaptiveExtension();
URL url = URL.valueOf("test://localhost/test?info.service=b");
adaptiveExtension.passInfo("james", url);
要使用dubbo的自适应机制,入参必须带有URL参数。
使用重点,URL 的格式:
info.service=a 的参数名格式,是接口类 InfoService 的驼峰大小写拆分
Dubbo SPI 的核心实现类为 ExtensionLoader,此类的使用几乎遍及 Dubbo 的整个源
码体系。
ExtensionLoader 有三个重要的入口方法,分别与@SPI、@Activate、@Adaptive 注
解对应。
getExtension 方法,对应加载所有的实现
getActivateExtension 方法,对应解析加载@Activate 注解对应的实现
getAdaptiveExtension 方法,对应解析加载@Adaptive 注解对应的实现
其中,
@Adaptive 注解作的自适应功能,还涉及到了代理对象(而 Dubbo 的代理机制,有两种选择,jdk 动态代理和 javassist 动态编译类)。我们将后后续篇章对此进行说明。