上篇我们介绍了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);}//对类进行iocinjectExtension(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();//如果没有配置keyif (name == null || name.length() == 0) {//如果类有Extension注解,则是注解的value,如果没注解则是类名称的小写做为namename = 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写完,太卷了!
知识总结,分享不易,全文手敲,欢迎大家点赞评论收藏。