本文基于dubbo2.7.7对如下三个问题分析
@SPI
注解的作用@Adaptive
注解的作用,放在Type和Method上的区别和注意点- 什么时候动态生成和编译
xxx$Adaptive
代码
@SPI
注解
- dubbo中自定义了
SPI
机制,将接口和具体实现类分开,一个SPI接口可能有多种实现,用户也可以通过SPI实现自己的扩展。在META-INF/dubbo/internal
、META-INF/dubbo
、META-INF/services
三个目录下选择一个,新建名为接口全类名的文件,并在文件中填写扩展名extName和具体实现类, 扩展名任意, 但不要重复. - 注意: dubbo在扫描SPI的实现类时内部是通过Map保存,无法保证顺序. 但可以按照如下顺序,
META-INF/dubbo/internal
、META-INF/dubbo
、META-INF/services
优先级从高到低使用相同扩展名, 覆盖默认实现。
dubbo通过ExtensionLoader
来加载这些实现类。dubbo的spi接口必须要加上的@SPI
注解, 否则在加载时会抛出异常:
@SPI
注解上的value值用于指定默认实现的名字, 当加载实现类之前, 会先从接口的@SPI
注解上获取value值,如果未指定,调用ExtensionLoader.getExtensionLoader(CustomInterface.class).getDefaultExtension()
返回的是null
@Adaptive
注解
ExtensionLoader#getAdaptiveExtension();
是dubbo源码中使用比较多的方案,主要用于在spi多个实现类中找一个数据合适的扩展实现。在dubbo中该方法返回的实现类可以理解为是spi接口的一个代理类,是调用其它实现类的入口。
而@Adaptive
注解则是用来帮助ExtensionLoader#getAdaptiveExtension();
判断要获取哪个实现类.
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
String[] value() default {};
}
- value指定的值只有在@Adaptive放在interface的method上时才会有作用, 放在具体实现类class上时根本不会读取该value的值
- value的值即为key名称,dubbo会通过key名称查找对应的扩展名extName,例如
String extName = url.getParameter(“transporter”, “netty”),
但对部分spi接口做了特殊处理,例如Protocol,不是调用getParameter()方法获取extName, 如下:
String extName = (url.getProtocol() == null ? “dubbo” : url.getProtocol()); - value可以指定多个,按照数组顺序, 优先级从高到底, 直到查到为止.
如果没有找到value中对应的扩展, 则使用接口的SimpleName, 以驼峰处添加点分隔符,字符串全部转成小写,作为参数获取获取extName例如org.apache.dubbo.xxx.YyyInvokerWrapper, 转成 yyy.invoker.wrapper - 调用ExtensionLoader#getExtension(java.lang.String extName)方法获取该扩展名对应的具体实例对象,未获取到则报错
从@Adaptive
注解定义可以看出既可以放在类上, 又可以放在方法上. 但却有着不同的意义和响应的限制
用在SPI实现类上作用及注意点
- 只能放在实现类上,放在spi接口上不会被dubbo处理,无意义;
- 如果多个实现类有
@Adaptive
注解, 会更具SPI目录的优先级覆盖;但同在META-INF/dubbo/internal
目录下,同类名的话则报错;如果已经有一个带有@Adaptive
注解的实现类,再调用ExtensionLoader#addExtension
则报错.
这样设计的原因其实很简单:只能有一个实现类带有@Adaptive
注解, dubbo内部带有@Adaptive
注解的实现类又希望可以被用户覆盖. - 放在实现类上时,则不会再动态生成
xxx$Adaptive
类。实际上dubbo源码中也就是org.apache.dubbo.common.compiler.support.AdaptiveCompiler
和org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
这样使用, 其它都是放在SPI接口方法上.
用在SPI接口方法上作用及注意点
- 放在SPI接口的方法上
@Adaptive
注解中的value属性,才会被处理生效 - 动态生成
xxx$Adaptive
类,动态编译class,例如:org.apache.dubbo.rpc.Protocol
, 则生成Protocol$Adaptive
源码并编译成class - 当实现类和接口都不存在
@Adaptive
时,调用#getAdaptiveExtension();
则会报如下异常:
什么时候动态生成$Adaptive代码
ExtensionLoader
在从META-INF/dubbo/internal
、META-INF/dubbo
、META-INF/services
三个目录加载SPI实现类时,如果实现类上存在@Adaptive
注解,则会被缓存到成员变量cachedAdaptiveClass
,当调用ExtensionLoader#getAdaptiveExtension()
会先判断实例是否存在,不存在则通过cachedAdaptiveClass
创建对象,如果cachedAdaptiveClass
的仍然为空,则更具SPI接口动态创建xxx$Adaptive
代码,xxx代表具体的SPI接口。此时要求接口中必须有@Adaptive
注解的方法, 否则报错. 参见源码:org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator
org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator#generate()
动态生成的$Adaptive代码内部是什么逻辑
对方法参数进行null值的一些常规校验, 然后根据@Adaptive注解的value属性值获取SPI扩展名和方法返回值类型获取对应的SPI实现类。
例如org.apache.dubbo.rpc.Protocol
接口动态生成的Protocol$Adaptive
代码如下:
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
public java.util.List getServers() {
throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
if (arg1 == null)
throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)
ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
public void destroy() {
throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null)
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)
ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
}