Dubbo spi机制是在jdk spi机制的基础上做了功能增强处理,在实际使用过程中是否需要dubbo的spi机制具体还是要看业务场景,在介绍dubbo spi之前,我们先对jdk的spi机制做个简单了解。
SPI英文全称Service Provider Interface,中译服务提供接口,先看下它的运行机制:将接口的服务实现类的全限定名配置在文件中,在具体使用服务过程中通过服务加载器读取配置文件,加载具体的服务实现类。 接下来再看下它的一个基本使用。
-
首先需要定义一个接口类,如Action: public interface Action {
void doAction();
}
-
根据实际业务逻辑创建一个实现类,如HelloAction public class HelloAction implements Action {
public HelloAction() {
System.out.println("****HelloAction constructor******");
}
@Override
public void doAction() {
System.out.println("*****************HelloAction***********************");
}
}
-
定义配置文件,在META-INF/services目录下(没有就新建)新建一个以接口类全限定名命名的文件,如:org.xx.xx.Action,在文件中添加具体的实现,内容为实现类全限定名,如果有多个实现类以换行符为分隔符 org.xx.xx.HelloAction
org.xx.xx.WorldAction
-
最后一步就是编写具体的服务查找代码 ServiceLoader<Action> serviceLoader = ServiceLoader.load(Action.class);
Iterator<Action> iterator = serviceLoader.iterator();
while (iterator.hasNext()){
iterator.next().doAction();
}
Jdk spi机制有几个约定:
- 服务接口配置文件必须在classpath的META-INF/services目录下。
- 实现类要有无参构造函数。
- 服务提供接口可以是接口类或抽象类,其实用具体的实现类也是可以正常运行的,但是没意义。
Dubbo与jdk最大的不同是对扩展实现这块做了分类处理,它将服务实现分为以下几类:
- 包装扩展点:服务实现类以服务接口作为构造函数参数,包装的对象是普通扩展点和集合扩展点,一般是用来增加额外逻辑处理,比如调用的Filter,引用暴露的Listener。
- 集合扩展点:部分场景中需要同时使用多个实现的时候,可以用dubbo内置的激活机制来获取。
- 自适应扩展点:设计出它的目的就是为了能够在程序执行过程中动态调整所需的扩展实现。目前dubbo中的自适应扩展点分为两种,一是通过@Adaptive来指定一个扩展实现作为自适应扩展点,二是dubbo根据预先设定的逻辑自动生成一个代理类在执行方法时根据参数动态选择具体扩展实现。
- 普通扩展点:除了包装扩展点和自适应扩展点之外的实现。
dubbo spi的加载过程:
dubbo自定义了扩展加载器ExtensionLoader,加载服务实现的过程中,首先会通过加载策略加载接口的所有服务实现类,再使用反射机制实例化具体服务实现,如果有需要还会对其属性做IOC操作,对其本身做包装操作。
dubbo spi的基本使用:
-
定义接口并使用@SPI进行类注解,以dubbo内置的org.apache.dubbo.rpc.Filter接口为例 @SPI
public interface Filter {
Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;
interface Listener {
void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation);
void onError(Throwable t, Invoker<?> invoker, Invocation invocation);
}
}
-
创建实现类ProviderGenericFilter(ps:invoke方法中的业务逻辑可以根据自己的业务需要编写代码) public class ProviderGenericFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Class<?>[] parameterTypes = invocation.getParameterTypes();
if(parameterTypes.length == 0){
return invoker.invoke(invocation);
}
String[] parameterTypeArray = new String[parameterTypes.length];
for(int index = 0;index<parameterTypes.length;index++){
parameterTypeArray[index] = parameterTypes[index].getName();
}
try {
Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), invocation.getMethodName(), parameterTypeArray);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(int i = 0;i<invocation.getArguments().length;i++){
Object argument = invocation.getArguments()[i];
if(argument != null && genericParameterTypes[i] instanceof ParameterizedType ){
Object realize = PojoUtils.realize(argument, parameterTypes[i], genericParameterTypes[i]);
invocation.getArguments()[i] = realize;
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return invoker.invoke(invocation);
}
-
配置服务实现,在META-INF/dubbo/目录下创建org.apache.dubbo.rpc.Filter文件名,然后以键值对的格式来配置服务实现: providerGenericFilter=org.xx.Filter.ProviderGenericFilter -
获取服务实现 ExtensionLoader.getExtensionLoader(Action.class).getExtension(“providerGenericFilter”)
自适应扩展点的使用:
-
定义接口并使用@SPI进行类注解,可以配置默认的扩展名 @SPI("refer")
public interface IListener {
@Adaptive
void request(URL url);
}
要使用自适应机制,需要采用@Adaptive注解。这里有两种情况需要区分对待:注解在服务实现类上时,则会把当前类当作自适应扩展点来调用,注解在接口方法上,则dubbo会按照预先设定的逻辑生成一个代理类,在真正调用方法时会根据URL配置找到真实使用的扩展实现。 -
创建实现,这里我创建了两个实现ReferListener和ExportListener以便清晰展示动态调整扩展点 public class ReferListener implements IListener {
@Override
public void request(URL url) {
System.out.println("ReferListener ------>>>request");
}
}
public class ExportListener implements IListener {
@Override
public void request(URL url) {
System.out.println("ExportListener ------>>>request");
}
}
-
配置服务实现,这点和基本使用中的一样 export=org.nickyu.spi.ExportListener
refer=org.nickyu.spi.ReferListener
-
自适应扩展的具体使用 public static void main(String[] args) {
IListener adaptiveExtension = ExtensionLoader.getExtensionLoader(IListener.class).getAdaptiveExtension();
URL url = new URL("", "", 0).addParameter("i.listener","refer");
adaptiveExtension.request(url);
}
结果输出:
将refer替换成export,运行并观察结果
dubbo默认会将当前类名加以处理当作URL中的key来获取扩展点,如果不想用默认值也可以通过@Adaptive来指定key
集合扩展点的使用:
每个扩展点都需要@Activate进行注解并配置好组名,dubbo通过匹配定义好的组名来查找具体的扩展实现,以下举例获取Dubbo的Filter接口provider组实现:
URL url = new URL("", "", -1);
String[] names = new String[]{};
List<Filter> activateExtension = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(url, names, CommonConstants.PROVIDER);
有的扩展实现还会在@Activate注解上定义value,这是个数组,想要获取这样的扩展点还需要在URL中配置指定key(可以简单理解为value中的值)和其参数值,以下举例在获取组provider的具体实现基础上额外获取CacheFilter:
URL url = new URL("", "", -1).addParameter("cache", "lru");
String[] names = new String[]{};
List<Filter> activateExtension = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(url, names, CommonConstants.PROVIDER);
|