概述
1.背景,改进
SPI的全名为Service Provider Interface,面向对象的设计里面,模块之间推荐基于接口编程,而不是对实现类进行硬编码,这样做也是为了模块设计的可拔插原则。为了在模块装配的时候不在程序里指明是哪个实现,就需要一种服务发现的机制,jdk的spi就是为某个接口寻找服务实现
了解Dubbo/阅读过官网文档的同学,可以看到Dubbo内部存在着大量的扩展,如Protocol:DubboProtocol,InJvmProtocol; Cluster:FailoverCluster,FailFastCluser;LoadBalance:RandomLoadBalance,LeastActiveLoadBalance等等;
Dubbo的SPI扩展机制主要在JDK的扩展机制上进行了改进:主要原因如下
- 需要遍历所有的实现,并实例化,然后我们在循环中才能找到我们需要的实现。
- 配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们。
- 扩展如果依赖其他的扩展,做不到自动注入和装配
- 不提供类似于Spring的IOC和AOP功能
- 扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring bean,原生的Java SPI不支持
2.Dubbo—SPI常见术语(摘自官网)
2.1 扩展点(Extension Point)
是一个Java的接口。
2.2 扩展(Extension)
扩展点的实现类。
2.3 扩展实例(Extension Instance)
扩展点实现类的实例。
2.4 扩展自适应实例(Extension Adaptive Instance)
第一次接触这个概念时,可能不太好理解(我第一次也是这样的...)。如果称它为扩展代理类,可能更好理解些。扩展的自适应 实例其实就是一个Extension的代理,它实现了扩展点接口。在调用扩展点的接口方法时,会根据实际的参数来决定要使用哪个扩 展。比如一个IRepository的扩展点,有一个save方法。有两个实现MysqlRepository和MongoRepository。IRepository的自适应实例在调用接口方法的时候,会根据save方法中的参数,来决定要调用哪个IRepository的实现。如果方法参数中有repository=mysql,那么就调用MysqlRepository的save方法。如果repository=mongo,就调用MongoRepository的save方法。和面向对象的延迟绑定很类似。为什么Dubbo会引入扩展自适应实例的概念呢?
- Dubbo中的配置有两种,一种是固定的系统级别的配置,在Dubbo启动之后就不会再改了。还有一种是运行时的配置,可能对于每一次的RPC,这些配置都不同。比如在xml文件中配置了超时时间是10秒钟,这个配置在Dubbo启动之后,就不会改变了。但针对某一次的RPC调用,可以设置它的超时时间是30秒钟,以覆盖系统级别的配置。对于Dubbo而言,每一次的RPC调用的参数都是未知的。只有在运行时,根据这些参数才能做出正确的决定。
- 很多时候,我们的类都是一个单例的,比如Spring的bean,在Spring bean都实例化时,如果它依赖某个扩展点,但是在bean实例化时,是不知道究竟该使用哪个具体的扩展实现的。这时候就需要一个代理模式了,它实现了扩展点接口,方法内部可以根据运行时参数,动态的选择合适的扩展实现。而这个代理就是自适应实例。 自适应扩展实例在Dubbo中的使用非常广泛,Dubbo中,每一个扩展都会有一个自适应类,如果我们没有提供,Dubbo会使用字节码工具为我们自动生成一个。所以我们基本感觉不到自适应类的存在。后面会有例子说明自适应类是怎么工作的。
2.5 @SPI
@SPI注解作用于扩展点的接口上,表明该接口是一个扩展点。可以被Dubbo的ExtentionLoader加载。如果没有此ExtensionLoader调用会异常。
2.6 @Adaptive
@Adaptive注解用在扩展接口的方法上。表示该方法是一个自适应方法。Dubbo在为扩展点生成自适应实例时,如果方法有@Adaptive注解,会为该方法生成对应的代码。方法内部会根据方法的参数,来决定使用哪个扩展。 @Adaptive注解用在类上代表实现一个装饰类,类似于设计模式中的装饰模式,它主要作用是返回指定类,目前在整个系统中AdaptiveCompiler、AdaptiveExtensionFactory这两个类拥有该注解。
2.7 ExtentionLoader
类似于Java SPI的ServiceLoader,负责扩展的加载和生命周期维护。
2.8 扩展别名
和Java SPI不同,Dubbo中的扩展都有一个别名,用于在应用中引用它们。比如
random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
roundrobin=com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
其中的random,roundrobin就是对应扩展的别名。这样我们在配置文件中使用random或roundrobin就可以了。
2.9 一些路径
和Java SPI从/META-INF/services
目录加载扩展配置类似,Dubbo也会从以下路径去加载扩展配置文件:
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
3.Dubbo-SPI机制测试
项测项目结构,定义Roboot接口,定义void sayHello()方法,注意Roboot接口定义SPI注解,SPI(key)表示默认为value(对应键值对中的key);
两个Roboot实现类,Bumbleee,OptimusPrime;
Bumbleee类:
package com.didispace.spi.impl;
import com.didispace.spi.Robot;
/**
* @Author: SoftWareKang
* @Name:SpringCloud-Learning
* @Date: 2020/5/24 17:33
*/
public class Bumblebee implements Robot {
public static final String NAME = "bumblebee";
@Override
public void sayHello() {
System.out.println("I'm Bumblebee");
}
}
OptimusPrime类:
package com.didispace.spi.impl;
import com.didispace.spi.Robot;
/**
* @Author: SoftWareKang
* @Name:SpringCloud-Learning
* @Date: 2020/5/24 17:33
*/
public class OptimusPrime implements Robot {
public static final String NAME = "optimusPrime";
@Override
public void sayHello() {
System.out.println("I'm OptimusPrime");
}
}
key-value文件:META-INF/dubbo/com.didispace.spi.Robot,文件内容如下
optimusPrime = com.didispace.spi.impl.OptimusPrime bumblebee = com.didispace.spi.impl.Bumblebee
测试类:
@Test
public void spiTest() {
ExtensionLoader<Robot> extensionLoader =
ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot bumblebee = extensionLoader.getExtension("bumblebee");
bumblebee.sayHello();
}
测试结果:控制台会打印如下结果;
I'm OptimusPrime
I'm Bumblebee
4.基本用法了解,探测源码
ExtensionLoader 是最核心的类,负责扩展点的加载和生命周期管理。 ExtensionLoader 的方法比较多,比较常用的方法有:
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) public T getExtension(String name) public T getAdaptiveExtension()
getExtensionLoader方法 这是一个静态工厂方法,入参是一个可扩展的接口,返回一个该接口的ExtensionLoader实体类。通过这个实体类,可以根据name获得具体的扩展,也可以获得一个自适应扩展。
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
// type如果为空返回参数异常;
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
// 如果type不是接口类型,返回参数异常
} else if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
// 如果没有@SPI注解,返回参数异常
} else if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
} else {
// 获取ExtensionLoader实例
ExtensionLoader<T> loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
if (loader == null) {
// 放入缓存中
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
// loader赋值
loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
}
return loader;
}
}
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
// 根据name即key获取对用接口实现类的实例
public T getExtension(String name) {
// 判断参数:如果name不为null或不为"",否则返回参数异常
if (name != null && name.length() != 0) {
if ("true".equals(name)) {
return this.getDefaultExtension();
} else {
// 缓存中获取holdler:可以理解为对一个对象持有的工具类
Holder<Object> holder = (Holder)this.cachedInstances.get(name);
if (holder == null) {
// 没有则创建holder
this.cachedInstances.putIfAbsent(name, new Holder());
holder = (Holder)this.cachedInstances.get(name);
}
// 获取该对象实例的值
Object instance = holder.get();
// 标准的双重判断
if (instance == null) {
synchronized(holder) {
instance = holder.get();
if (instance == null) {
// 核心方法,创建实例
instance = this.createExtension(name);
holder.set(instance);
}
}
}
return instance;
}
} else {
throw new IllegalArgumentException("Extension name == null");
}
}
// holder类
public class Holder<T> {
private volatile T value;
public Holder() {
}
public void set(T value) {
this.value = value;
}
public T get() {
return this.value;
}
}
核心方法private T createExtension(String name)方法
private T createExtension(String name) {
// 根据扩展节点名称获取对应class
Class<?> clazz = (Class)this.getExtensionClasses().get(name);
if (clazz == null) {
throw this.findException(name);
} else {
try {
// 缓存中获取实例
T instance = EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
// 如果为空,调用newInstance()方法生成实例
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = EXTENSION_INSTANCES.get(clazz);
}
// IOC注入属性
this.injectExtension(instance);
// 如果有wrapper,添加wrapper
Set<Class<?>> wrapperClasses = this.cachedWrapperClasses;
Class wrapperClass;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
for(Iterator i$ = wrapperClasses.iterator(); i$.hasNext(); instance = this.injectExtension(wrapperClass.getConstructor(this.type).newInstance(instance))) {
wrapperClass = (Class)i$.next();
}
}
return instance;
} catch (Throwable var7) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + this.type + ") could not be instantiated: " + var7.getMessage(), var7);
}
}
}
核心: private Map<String, Class<?>> getExtensionClasses()方法
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = (Map)this.cachedClasses.get();
if (classes == null) {
Holder var2 = this.cachedClasses;
synchronized(this.cachedClasses) {
classes = (Map)this.cachedClasses.get();
if (classes == null) {
// 根据配置,加载classes
classes = this.loadExtensionClasses();
this.cachedClasses.set(classes);
}
}
}
return classes;
}
核心:private Map<String, Class<?>> loadExtensionClasses()方法,private void loadFile(Map<String, Class<?>> extensionClasses, String dir)方法
private Map<String, Class<?>> loadExtensionClasses() {
// 获取SPI默认值
SPI defaultAnnotation = (SPI)this.type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if (value != null && (value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
// 默认值只能唯一
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + this.type.getName() + ": " + Arrays.toString(names));
}
if (names.length == 1) {
// 缓存
this.cachedDefaultName = names[0];
}
}
}
Map<String, Class<?>> extensionClasses = new HashMap();
// 加载三个目录下的配置
this.loadFile(extensionClasses, "META-INF/dubbo/internal/");
this.loadFile(extensionClasses, "META-INF/dubbo/");
this.loadFile(extensionClasses, "META-INF/services/");
return extensionClasses;
}
// 加载的核心方法,extensionClasses缓存class
private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
// 拼接fileName
String fileName = dir + this.type.getName();
try {
// 加载器
ClassLoader classLoader = findClassLoader();
Enumeration urls;
// 根据文件名获取所有URL
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
label269:
while(urls.hasMoreElements()) {
java.net.URL url = (java.net.URL)urls.nextElement();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
try {
String line = null;
while(true) {
do {
if ((line = reader.readLine()) == null) {
continue label269;
}
// 定位#字符,截取,因为是注释
int ci = line.indexOf(35);
if (ci >= 0) {
line = line.substring(0, ci);
}
// 去除空格
line = line.trim();
} while(line.length() <= 0);
try {
String name = null;
int i = line.indexOf(61);
if (i > 0) {
// 获取key-value
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
// class.forname获取class
Class<?> clazz = Class.forName(line, true, classLoader);
if (!this.type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " + this.type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface.");
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (this.cachedAdaptiveClass == null) {
this.cachedAdaptiveClass = clazz;
} else if (!this.cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: " + this.cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName());
}
} else {
try {
// // 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
clazz.getConstructor(this.type);
Set<Class<?>> wrappers = this.cachedWrapperClasses;
if (wrappers == null) {
this.cachedWrapperClasses = new ConcurrentHashSet();
wrappers = this.cachedWrapperClasses;
}
// 缓存clazz
wrappers.add(clazz);
} catch (NoSuchMethodException var27) {
clazz.getConstructor();
if (name == null || name.length() == 0) {
name = this.findAnnotationName(clazz);
if (name == null || name.length() == 0) {
if (clazz.getSimpleName().length() <= this.type.getSimpleName().length() || !clazz.getSimpleName().endsWith(this.type.getSimpleName())) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
}
name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - this.type.getSimpleName().length()).toLowerCase();
}
}
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
Activate activate = (Activate)clazz.getAnnotation(Activate.class);
if (activate != null) {
this.cachedActivates.put(names[0], activate);
}
String[] arr$ = names;
int len$ = names.length;
for(int i$ = 0; i$ < len$; ++i$) {
String n = arr$[i$];
if (!this.cachedNames.containsKey(clazz)) {
this.cachedNames.put(clazz, n);
}
Class<?> c = (Class)extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + this.type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
}
} catch (Throwable var28) {
IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + this.type + ", class line: " + line + ") in " + url + ", cause: " + var28.getMessage(), var28);
this.exceptions.put(line, e);
}
}
} finally {
reader.close();
}
} catch (Throwable var30) {
logger.error("Exception when load extension class(interface: " + this.type + ", class file: " + url + ") in " + url, var30);
}
}
}
} catch (Throwable var31) {
logger.error("Exception when load extension class(interface: " + this.type + ", description file: " + fileName + ").", var31);
}
}
loadExtensionClasses()的基本流程:
1.SPI注解解析
2.调用 loadDirectory 方法加载指定文件夹配置文件。
3.loadResource 方法用于读取和解析配置文件,并通过反射加载类;
4.缓存相关信息
5.Dubbo-IOC源码
private T createExtension(String name)方法在创建完实例后,执行// IOC注入属性 this.injectExtension(instance);
我们进入源码分析
private T injectExtension(T instance) {
try {
if (this.objectFactory != null) {
// 获取所有方法
Method[] arr$ = instance.getClass().getMethods();
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; ++i$) {
// 如果方法为set方法,且级别为public
Method method = arr$[i$];
if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) {
Class pt = method.getParameterTypes()[0];
try {
// 获取属性名
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
// 通过工厂生成对象
Object object = this.objectFactory.getExtension(pt, property);
// 通过反射赋值
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception var9) {
logger.error("fail to inject via method " + method.getName() + " of interface " + this.type.getName() + ": " + var9.getMessage(), var9);
}
}
}
}
} catch (Exception var10) {
logger.error(var10.getMessage(), var10);
}
return instance;
}
在上面代码中,objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展。
Dubbo-IOC目前主要是set方法,很简单;
6.总结
- 生成接口类的ExtensionLoader对象:
- 参数判空,接口类型校验,@SPI注解校验
- 生成ExtensionLoader,objectFactory;
- 根据ExtensionLoader.getExtensionName(name)获取扩展节点实例;
- 查询缓存的handler,没有则生成handler;查询handler的值,为空则createExtension(name)创建对应实例;
- 根据配置,底层根据class.forName生成class对象实例,返回class对象
- 调用class.newInstance创建实例,初始化,IOC赋值等;
- 根据生成的实例,进行面向接口编程;
最后
以上就是秀丽小蝴蝶为你收集整理的Dubbo源码解析-SPI.Dubbo可扩展机制原理的全部内容,希望文章能够帮你解决Dubbo源码解析-SPI.Dubbo可扩展机制原理所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复