上篇我们介绍了Dubbbo整合Spring中的@DubboComponentScan注解源码分析,地址如下
Dubbo源码解读-dubbo启动与Spring整合之@ DubboComponentScan-CSDN博客
本文主要针对Dubbo的SPI机制,从dubbo源码角度解析。
Dubbo SPI机制,是Dubbo中比较重要的技术手段,也是面试过程中比较常问的技术问题,大家可以好好仔细读一下本文。有疑问欢迎留言。
接着说明,读Dubbo源码最好是先对Spring源码有一定的了解。如果大家需要,我也可以针对Spring框架做一系列源码的解读专栏。
不过不用担心,如果需要Spring的源码知识,文章中也会进行Spring源码铺垫介绍的。
如果内容中有没描述清楚的,或者大家在阅读源代码有疑问的,欢迎留言,看到就会及时回复。
为了更清楚的分析解释源码,源代码中部分不重要的内容可能会删减,保留重要内容方便大家理解。
白天没时间写文章,凌晨两点写作此文,实属不易呀兄弟们!
本文主要内容
- SPI机制好处
- 自定义SPI接口如何实现
- SPI机制加载的文件目录
- getExtension(String name):源码解读。
- Dubbo中SPI主要方法
SPI机制优势:
主要方便扩展,符合基本开闭原则。
自定义SPI接口如何实现
- 定义接口
- 接口增加@SPI注解
- 实现扩展类
- 指定文件夹下配置扩展文件,内容:key:class
#如dubbo.jar中META-INF/dubbo/internal目录下com.alibaba.dubbo.rpc.cluster.Cluster文件内容 mock=com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper failover=com.alibaba.dubbo.rpc.cluster.support.FailoverCluster failfast=com.alibaba.dubbo.rpc.cluster.support.FailfastCluster failsafe=com.alibaba.dubbo.rpc.cluster.support.FailsafeCluster failback=com.alibaba.dubbo.rpc.cluster.support.FailbackCluster forking=com.alibaba.dubbo.rpc.cluster.support.ForkingCluster available=com.alibaba.dubbo.rpc.cluster.support.AvailableCluster mergeable=com.alibaba.dubbo.rpc.cluster.support.MergeableCluster broadcast=com.alibaba.dubbo.rpc.cluster.support.BroadcastCluster
SPI机制加载文件目录
- META-INF/services/
- META-INF/dubbo/
- META-INF/dubbo/internal/
getExtension(String name):源码解读。
功能说明:
ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo")。此SPI方法是根据配置key名称获取对应的Protocol类。Dubbo其他SPI方法都与此方法比较类似,且基于此方法实现。所以从源码角度,重点讲一下此方法。
具体流程:
- 获取ExtensionLoader。每个SPI接口对应一个ExtensionLoader。
- cachedInstances:优先根据name从map中获取Holder<Object>
- map没有,则进行创建createExtension(name).红色表明的属性,会在其他SPI函数中使用。
- getExtensionClasses()。获取扩展类集合Map,缓存建立各种映射关系
- loadExtensionClasses:从本地文件中加载key和类的关系
- 设置cachedDefaultName:SPI注解的value
- 如果类有注解@Adaptive。设置cachedAdaptiveClass:@Adaptive
- 如果是包装类:cachedWrapperClasses:设置包装类型集合
- 其他:(不包括@Adaptive和包装类)
- cachedActivates:建立名称和类上@Activate注解映射Map<String, Activate>
- cachedNames:建立类和名称的映射ConcurrentMap<Class<?>, String>
- 建立名称和类的映射装成Map<String, Class<?>>
- 放入缓存cachedClasses:名称和类的映射装成Holder<Map<String, Class<?>>>
- loadExtensionClasses:从本地文件中加载key和类的关系
- .根据class从缓存EXTENSION_INSTANCES获取实例ConcurrentMap<Class<?>, Object>
- 缓存没有,根据class反射创建实例,放入缓存。
- IOC属性注入:injectExtension(),也是通过SPI形式,从扩展Factory中拿值。通过SpiExtensionFactory或者SpringExtensionFactory获取依赖对象。
- 遍历类方法中所有以set开头的
- 判断包装类集合是否为空【有可能返回实例,就不是名称对应的实例,而是被包装的实例】
- 不为空,则实例化包装类,且对包装类进行ioc,责任链模式(ProtocolFilterWrapper->ProtocolListenerWrapper->ProtocolListenerWrapper->DubboProtocol)
- getExtensionClasses()。获取扩展类集合Map,缓存建立各种映射关系
总结:
其实上面流程已经非常详细了,几乎已经是源代码级别了。但如果只是应付面试,想了解大概流程。可以简单总结如下。
- 根据本地文件夹夹在文件,建立key和class映射关系。
- 获取对应的class,进行反射实例化。
- IOC依赖注入,也是使用SPI实现。
- 判断类型对应的配置文件中是否包含包装类,如果存在则对当前类进行wrapper包装。
源码解析:
- ExtendLoader.getExtendloder()。流程1
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
//如果接口上没有@SPI注解,则报错
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
//从缓存中获取
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
//每一个@SPI接口类型都会对应一个ExtensionLoader对象
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
- ExtensionLoader.getEtension()。流程2
public T getExtension(String name) {
//缓存实例中取
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//创建实例
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
- ExtensionLoader.createExtension()。流程3
private T createExtension(String name) {
//根据配置的名称找到相应的类
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
//类实例化
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//对类进行ioc
injectExtension(instance);
//该类是否有包装类
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
//实例化包装类,且对包装类进行ioc,责任链模式
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}
- ExtensionLoader.loadExtensionClasses() 。流程3.1。设置cachedDefaultName,加载多目录
private Map<String, Class<?>> loadExtensionClasses() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
//如果@SPI注解中有value值
if ((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 " + type.getName()
+ ": " + Arrays.toString(names));
}
//把value值设置到 cachedDefaultName,,这个就是默认的实现类
if (names.length == 1) cachedDefaultName = names[0];
}
}
/**
* 从下面的地址中加载这个类型的数据到extensionClasses中
* META-INF/dubbo/internal/
* META-INF/dubbo/
* META-INF/services/
*/
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadDirectory(extensionClasses, DUBBO_DIRECTORY);
loadDirectory(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
- ExtensionLoader.loadClass() 。流程3.1.1这部是最主要的。设置ExtensionLoader类中各种属性。(cachedAdaptiveClass,cachedWrapperClasses,cachedActivates,cachedNames)
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
//如果类类型和接口类型不一致,报错
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
//如果类上面有@Adaptive注解
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
//把类赋值给cachedAdaptiveClass,,这个cachedAdaptiveClass后面要返回的
cachedAdaptiveClass = clazz;
//如果有超过一个实现类上面有@Adaptive注解,报错
} else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()
+ ", " + clazz.getClass().getName());
}
//如果是包装类,包装类必然是持有目标接口的引用的,有目标接口对应的构造函数
} else if (isWrapperClass(clazz)) {
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
//把类添加到包装类集合中
wrappers.add(clazz);
} else {
//获取类的无参构造函数,如果是包装类,这里会报错,,其实这里包装类走不进来了,包装类先处理的
clazz.getConstructor();
//如果没有配置key
if (name == null || name.length() == 0) {
//如果类有Extension注解,则是注解的value,如果没注解则是类名称的小写做为name
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
//如果类上面有@Activate注解,则建立名称和注解的映射
cachedActivates.put(names[0], activate);
}
for (String n : names) {
if (!cachedNames.containsKey(clazz)) {
//这里建立类和名称的关系
cachedNames.put(clazz, n);
}
Class<?> c = extensionClasses.get(n);
if (c == null) {
//这个map是要返回的,建立名称和类的关系
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
Dubbo中SPI其他常用方法
getActivateExtension(url,value[],group)
此方法主要获取扩展类中含有@Activate注解的类集合,然后再根据url、value以及group对类集合进行过滤,获取匹配的SPI类
因为依赖的源码上面已经讲的很清晰了,这里就简单介绍一下流程了。
具体流程如下:
- 延用getExtensionClasses();流程如3.
- 遍历缓存cachedActivates,Map<String, Activate>
- 优先匹配group.
- 根据URL参数匹配@Activate的value属性
- 根据values[]匹配
getAdaptiveExtension()。
此方主要获取SPI中,有@Adaptive注解的类。
重点注意一下
-
如果存在两个就会报错
- 如果不存在,但是SPI对应接口上有注解,则会通过javassist动态生成一个代理类。如
具体流程如下:
- 根据@Adaptive注解获取实例
- 如果获取不到,判断SPI接口方法是否有主机@Adaptive。动态生成字节码,创建对应的代理类。
总结:上面内容中,每个从业务流程和源码角度进行了详细分析,如果大家有疑问或者对文章排版任何方面有建议都可以留言评论,看到都会及时回复大家。
凌晨3.26写完,太卷了!
知识总结,分享不易,全文手敲,欢迎大家点赞评论收藏。
标签:Dubbo,name,dubbo,clazz,SPI,源码,class From: https://blog.csdn.net/u014336799/article/details/136612692