本文对应源码地址:https://github.com/nieandsun/dubbo-study
注意:
dubbo 要求SPI扩展点的实现类必须要有一个无参构造,除了Wrapper实现类之外
文章目录
上篇文章《【SPI】 — java spi 机制简介》中, 可以看到,java spi 机制非常简单, 就是读取指定的配置文件, 将所有的类都加载到程序中。 而这种机制, 存在很多缺陷, 比如:
- 所有实现类无论是否使用, 直接被加载, 可能存在浪费
- 不能够灵活控制什么时候什么时机, 匹配什么实现, 功能太弱
Dubbo 基于自己的需要,对SPI 机制进行了增强, 本篇文章将简单介绍一下 Dubbo 中的 SPI用法。
1 @SPI 标签 及其使用简介
与 Java SPI 实现类配置不同, Dubbo SPI 是通过键值对的方式进行配置, 这样我们就可以按需加载指定的实现类。
@SPI注解的玩法如下:
- (1)在某个接口上标注@SPI注解,表明此接口是 SPI 的扩展点
@SPI("b") //指定缺省实现类的别名
public interface InfoService {
Object sayHello(String name);
}
- (2)在META-INF下的dubbo文件夹下指定接口与实现类之间的映射关系,具体配置如下:
-(3)简单看一下某一个实现类的源代码:
public class InfoServiceAImpl implements InfoService {
@Override
public Object sayHello(String name) {
System.out.println(name + ",你好,调通了A实现!");
return name + ",你好,调通了A实现!";
}
}
- (4)通过(2)中指定的实现类的key获取具体的实现类
/**
* dubbo spi类加载验证
* extensionLoader.getExtension("a") ---------- 取到对应的扩展类
* extensionLoader.getDefaultExtension() ------ 取得SPI("b")指定的实现类(默认实现类)
*/
@Test
public void dubboSPI() {
//获取InfoService的 Loader 实例
ExtensionLoader<InfoService> extensionLoader = ExtensionLoader.getExtensionLoader(InfoService.class);
//取得a拓展类
InfoService infoServiceA = extensionLoader.getExtension("a");
infoServiceA.sayHello("yoyo");
//取得b拓展类
InfoService infoServiceB = extensionLoader.getDefaultExtension();
infoServiceB.sayHello("nrsc");
}
- (5)测试结果如下
2 @Adaptive标签 及其使用简介
2.1 @Adaptive标注在类上的具体含义详解 — 将该类作为最佳适配类
2.1.1 场景介绍
试想假如我写了一个接口,并想将其作为dubbo的SPI扩展点,但却并没在该接口上标注一个
【缺省实现类的别名】
----> 也就是说没有指定默认的实现类。
但是我又想让该扩展点有一个【不得不走的默认实现】
,并且在该默认实现里我可以拿到其他非默认实现,这该怎么搞呢???
有了以上需求后,回顾一下1中的用法,做个简图如下:
回顾完之后你应该会有这样的思路:
我可以先通过extensionLoader.getExtension(【不得不走的默认实现】的别名
),获取到该默认实现类,然后在该默认实现里再遍历获取到所有的非默认实现!!!
是的,可以做到,但是有点不够优雅:
- 首先,按照这种思路这个
【不得不走的默认实现】的别名
应该得写死在调用的代码里 - 其次,在这个不得不走的默认实现类里,找其他非默认实现类的的方法还得自己去写
而@Adaptive注解标注在类上的使用姿势就优雅地解决了上诉问题。
2.1.2 @Adaptive标注在类上使用姿势及其意义详解
使用姿势介绍
(1)在某个接口上标注@SPI注解,表明此接口是 SPI 的扩展点
(2)在某个具体实现类上标注上@Adaptive 表明该实现类是【不得不走的默认实现类】
,如下:
@Adaptive
public class InfoServiceAImpl implements InfoService {
@Override
public Object sayHello(String name) {
System.out.println(name + ",你好,调通了A实现!");
return name + ",你好,调通了A实现!";
}
}
(3)当然要像1中的(2)一样,在META-INF下的dubbo文件夹下指定接口与实现类之间的映射关系
(4)测试类如下:
@Test
public void test1() {
//获取InfoService的ExtensionLoader
ExtensionLoader<InfoService> loader = ExtensionLoader.getExtensionLoader(InfoService.class);
InfoService adaptiveExtension = loader.getAdaptiveExtension(); //获取到@Adaptive注解标注的实现类
System.out.println(adaptiveExtension.sayHello("yoyo"));
InfoService defaultExtension = loader.getDefaultExtension();//获取接口上@SPI("别名")中别名指定的实现类
System.out.println(defaultExtension.sayHello("nrsc"));
}
(5)通过打断点的方式来看一下,@Adaptive注解标注在类上如何优雅的解决了我在上面说的问题
由上图可知,其解决思路如下:
这里需要注意
的是:
- (1)生成的InfoService的ExtensionLoader会加入到ExtensionLoader中的一个ConcurrentMap缓存起来,因此我们假若在标有的@Adapter注解的实现类里再获取InfoService的ExtensionLoader,可以直接从ConcurrentMap缓存里取
- (2)遍历并执行所有非@Adapter注解标注的实现类的姿势如下:
//获取所有未标注@Adapter注解的实现类的别名,并通过别名获取到实现类
Set<String> supportedExtensions = loader.getSupportedExtensions();
for (String name : supportedExtensions) {
System.out.println(loader.getExtension(name).sayHello("MM"));
}
2.2 @Adaptive标注在方法上具体含义详解 — 静态代理该方法
2.2.1 场景介绍
现在有这样一个需求,一个接口下里有多个方法,但是我只想将某个方法暴漏给别人,该怎么搞???
相信大家肯定都知道,解决方式主要有两种:
- 动态代理
- 静态代理
而将@Adaptive注解标注在方法上就可以完成对该方法进行静态代理的工作 —》 据说因为静态代理比动态代理效率更高
,所以dubbo采用了静态代理 —> 有兴趣的可以进行考证!!!
2.2.2 @Adaptive标注在方法上的使用姿势
(1)
在某个接口上标注@SPI注解,表明此接口是 SPI 的扩展点
;
在该接口的某个方法上标注@Adapter注解,表明该方法需要被静态代理
,
并且注意这个方法必须要有一个参数为URL
(2)如1中的(2)一样,在META-INF下的dubbo文件夹下指定接口与实现类之间的映射关系
(3)某一个实现类的源码:
public class InfoServiceAImpl implements InfoService {
@Override
public Object sayHello(String name) {
System.out.println(name + ",你好,调通了A实现!");
return name + ",你好,调通了A实现!";
}
@Override
public Object passInfo(String msg, URL url) {
System.out.println("恭喜你,调通了A实现:" + url);
return msg;
}
}
(4)测试
【情况1】
各个实现类上都没有标注@Adaptive注解, 接口的SPI注解为@SPI(“b”),url无参数的情况
/**
* 各个实现类上都没有@Adaptive
* SPI上有注解@SPI("b"),
* url无参数
* 则以@SPI("b")为准,选择InfoServiceBImpl 实现中的方法进行静态代理
*/
@Test
public void test1() {
ExtensionLoader<InfoService> loader = ExtensionLoader.getExtensionLoader(InfoService.class);
InfoService adaptiveExtension = loader.getAdaptiveExtension();
URL url = URL.valueOf("test://localhost/test");
System.out.println(adaptiveExtension.passInfo("yoyo", url));
}
测试结果如下:
【情况2】
各个实现类上都没有标注@Adaptive注解, 接口的SPI注解为@SPI(“b”),URL中有具体的值info.service=a注意:
url中的info.service是按照接口名称InfoService拆解的结果,dubbo会按此规则找到InfoService接口别名为a的实现类,并对此实现类中的方法进行静态代理
/**
* 各个实现类上都没有@Adaptive
* SPI上有注解@SPI("b")
* URL中有具体的值info.service=a,
* 则以URL为准,选择InfoServiceAImpl 实现中的方法进行静态代理
*/
@Test
public void test2() {
ExtensionLoader<InfoService> loader = ExtensionLoader.getExtensionLoader(InfoService.class);
InfoService adaptiveExtension = loader.getAdaptiveExtension();
URL url = URL.valueOf("test://localhost/test?info.service=a");
System.out.println(adaptiveExtension.passInfo("james", url));
}
测试结果如下:
【情况3】
各个实现类上都没有标注@Adaptive注解, 接口方法中加上的注解为@Adaptive({“NRSC”}),URL中有具体的值为NRSC=c
- 此时InfoService接口应为:
@SPI("b") //指定缺省实现类的别名
public interface InfoService {
Object sayHello(String name);
@Adaptive({"NRSC"})
//@Adaptive
Object passInfo(String msg, URL url);
}
- 测试类为:
/**
* 各个实现类上面没有@Adaptive
* 接口方法中加上的注解为@Adaptive({"NRSC"}),
* URL中有具体的值NRSC=c,
* 则以URL中的NRSC参数为准,选择InfoServiceCImpl 实现中的方法进行静态代理
*/
@Test
public void test3() {
ExtensionLoader<InfoService> loader = ExtensionLoader.getExtensionLoader(InfoService.class);
InfoService adaptiveExtension = loader.getAdaptiveExtension();
URL url = URL.valueOf("test://localhost/test?info.service=a&NRSC=c");
System.out.println(adaptiveExtension.passInfo("james", url));
}
- 测试结果如下:
由此可知这种方式其实是对 【情况2】
的一种更优雅的实现!!!
【情况4】
InfoServiceCImpl实现类上有@Adaptive, 接口方法中加上的注解为@Adaptive({“NRSC”}),URL随意传值
/**
* InfoServiceCImpl实现类上有@Adaptive,URL随意传值
* 直接会拿到InfoServiceCImpl的实现类 ---》 并未做静态代理
*/
@Test
public void test4() {
ExtensionLoader<InfoService> loader = ExtensionLoader.getExtensionLoader(InfoService.class);
InfoService adaptiveExtension = loader.getAdaptiveExtension();
URL url = URL.valueOf("test://localhost/test");
System.out.println(adaptiveExtension.passInfo("james", url));
}
测试结果:
2.3 简单撸一下源码,看看为啥 @Adaptive标注在方法上会静态代理该方法,标注在类上却不会走静态代理
打着断点可以看到源码中ExtensionLoader中创建具体实现类的关键代码如下:
接下来我们看一下接口的任何实现类都没有标注@Adapter注解时,dubbo为我们拼接的代理类,即上图中code对应的字符串,将其格式化后如下:
package com.nrsc.service;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class InfoService$Adaptive implements com.nrsc.service.InfoService {
public java.lang.Object passInfo(java.lang.String arg0,
org.apache.dubbo.common.URL arg1) {
if (arg1 == null) {
throw new IllegalArgumentException("url == null");
}
org.apache.dubbo.common.URL url = arg1;
String extName = url.getParameter("NRSC", "b");
if (extName == null) {
throw new IllegalStateException(
"Failed to get extension (com.nrsc.service.InfoService) name from url (" +
url.toString() + ") use keys([NRSC])");
}
//通过别名找到具体的实现类
com.nrsc.service.InfoService extension = (com.nrsc.service.InfoService) ExtensionLoader.getExtensionLoader(com.nrsc.service.InfoService.class)
.getExtension(extName);
//拿着实现类调用方法
return extension.passInfo(arg0, arg1);
}
//未标注@Adapter注解的方法无法被调用
public java.lang.Object sayHello(java.lang.String arg0) {
throw new UnsupportedOperationException(
"The method public abstract java.lang.Object com.nrsc.service.InfoService.sayHello(java.lang.String) of interface com.nrsc.service.InfoService is not adaptive method!");
}
}
看到这段字符串,再联系一下上篇文章《【dubbo源码解析】— 通过javassist/JavassistCompiler动态生成一个实例对象》,是不是就可以很清楚的明白下面的结论了:
- @Adapter标签标注在方法上可以对该方法进行静态代理
- 如果接口的某个实现类上标注了@Adapter注解,将直接调用该实现类,而不会进行静态代理
end!!!