dubbo之SPI
作者:互联网
前言
dubbo应该是现阶段最流行的rpc框架之一。其中spi的机制贯穿着dubbo的整个架构。
SPI
SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。dubbo2.7.8Demo如下
public class ExtensionLoaderTest implements Transporter{
@Override
public RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException {
System.out.println("test yoyo bind");
return null;
}
@Override
public Client connect(URL url, ChannelHandler handler) throws RemotingException {
System.out.println("test yoyo connect");
return null;
}
public static void main(String[] args) throws RemotingException {
Transporter transporter = ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
URL url = URL.valueOf("dubbo://127.0.0.1:28092/?client=yoyo");
transporter.connect(url,null);
}
}
- 定义SPI接口,ps:这边直接借个了Transporter接口
- 创建对应Transporter接口的ExtensionLoader
- 通过ExtensionLoader生成Adaptive来实现动态化
ExtensionLoader
ExtensionLoader做为SPI的核心了,你结构如下
public class ExtensionLoader<T> {
// 全局容器缓存,表示spi接口class-ExtensionLoader的对应关系
// 通过getExtensionLoader(Class<T> type)方法创建,并缓存
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
// 全局实例缓存,spi接口实现class-其实例的对应关系
// 通过createExtension(String name, boolean wrap)方法创建,并缓存
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
// 当前spi接口class
private final Class<?> type;
// SPI容器接口,通过其默认实现AdaptiveExtensionFactory可集成各种容器
// 1. SpiExtensionFactory,通过ExtensionLoader.getExtensionLoader(type).getAdaptiveExtension()查找dubbo spi中实例
// 2. SpringExtensionFactory,通过BeanFactoryUtils.getOptionalBean查询spring中实例
// 通过 injectExtension 在创建 spi实例时,通过反射查询对应的setter方法将对应注入,实现Dubbo IOC
private final ExtensionFactory objectFactory;
// dubbo spi class的加载策略,分别对应
// 1.DubboInternalLoadingStrategy - META-INF/dubbo/internal/
// 2.DubboLoadingStrategy - META-INF/dubbo/
// 3.ServicesLoadingStrategy META-INF/services/
// 通过 loadExtensionClasses方法能三个目录进行扫描
private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
// 通过loadClass 加载dubbo spi class,如果发现有class实现Adaptive注解的记录它
// 当调用getAdaptiveExtensionClass获取AdaptiveClass时,如果发现cachedAdaptiveClass有值,则直接做为AdaptiveClass不再根据 spi Adaptive注解生成AdaptiveClass代码
private volatile Class<?> cachedAdaptiveClass = null;
// 通过loadClass 加载dubbo spi class,如果发现有class构造函数是type记录它
// 当调用createExtension创建spi实例时,如果此时wrap为true,则会用cachedWrapperClasses中符合Wrapper注解的类包装它,实现dubbo的 AOP
private Set<Class<?>> cachedWrapperClasses;
// 通过loadClass 加载dubbo spi class,存储其name-class的对应关系
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
// 对正常的spi 实现类,记录class - name的关系
// 方便通过getExtensionName根据class查找名字
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
// 对正常的spi 实现类,记录name - 类上面Activate注解的信息
// 方便通过getActivateExtension方法查找
private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
// 存储name- class实例,与cachedClasses对应
// 对过getExtension实行单例模式
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
// 存储type对就的AdaptiveClass实例,与cachedAdaptiveClass对应
private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
// 对就type接口上的SPI注解value,用于生成AdaptiveClass用
private String cachedDefaultName;
}
SPI调用流程
看一下源码。首先我们从这句话开始讲起
// ExtensionLoader.getExtensionLoader(Transporter.class)
// 1. 先检查有没有带SPI的注解,没有带,直接报错
// 2. new ExtensionLoader<T>(type)构造 ExtensionLoader
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type);
// 获取Adaptive实例主要三步骤
// 1. getAdaptiveExtensionClass 获取 Adaptive对象
// 2. newInstance 构建实例
// 3. injectExtension 实现IOC注入对象
public T getAdaptiveExtension();
private Class<?> getAdaptiveExtensionClass() {
// 触发SPI扫描流程,利用dubbo spi class的加载策略
getExtensionClasses();
// 如果扫摸到带有Adaptive注解的则直接使用它
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 利用接口中Spi注解和方法上Adaptive注解生成Class
return cachedAdaptiveClass createAdaptiveExtensionClass();
}
// Dubbo IOC 是通过 setter 方法注入依赖。
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
// 遍历目标类的所有方法
for (Method method : instance.getClass().getMethods()) {
// 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
// 获取 setter 方法参数类型
Class<?> pt = method.getParameterTypes()[0];
try {
// 获取属性名,比如 setName 方法对应属性名 name
String property = method.getName().length() > 3 ?
method.getName().substring(3, 4).toLowerCase() +
method.getName().substring(4) : "";
// 从 ObjectFactory 中获取依赖对象
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// 通过反射调用 setter 方法设置依赖
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method...");
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
例子中生成Transporter$Adaptive
public class Transporter$Adaptive implements org.apache.dubbo.remoting.Transporter {
public org.apache.dubbo.remoting.Client connect(org.apache.dubbo.common.URL arg0, org.apache.dubbo.remoting.ChannelHandler arg1) throws org.apache.dubbo.remoting.RemotingException {
if (arg0 == null)
throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
// client 值是 Adaptive 注解起的作用
// netty 默认值是Spi注解起的作用
String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
if (extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.remoting.Transporter) name from url (" + url.toString() + ") use keys([client, transporter])");
// 通过在url中取到的extName,然后去真实的实例
org.apache.dubbo.remoting.Transporter extension = (org.apache.dubbo.remoting.Transporter) ExtensionLoader.getExtensionLoader(org.apache.dubbo.remoting.Transporter.class).getExtension(extName);
return extension.connect(arg0, arg1);
}
// ...
}
总结Adaptive,spi注解的作用
- 在spi实现类中打上Adaptive,则直接使用它。
- 在spi接口方法打上Adaptive,作用在url参数,根据参数动态选择实例
- 在spi接口类上打上SPI,作为url上值为空时的默认值
主要参考
《Dubbo SPI》
《Dubbo SPI之Adaptive详解》
标签:dubbo,SPI,ExtensionLoader,private,spi,class 来源: https://blog.csdn.net/y3over/article/details/114709339