咱们都是知道一个合格的开源框架对于扩展的支持都要是至关弹性的,Dubbo 也不例外。Dubbo采用微内核+插件体系,使得设计优雅,扩展性强。Dubbo的扩展机制是基于SPI思想来实现的,可是并无采用JDK中原生的SPI机制。java
1.什么是SPIredis
java spi的具体约定为:当服务的提供者,提供了服务接口的一种实现以后,在jar包的META-INF/services/目录里同时建立一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能经过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不须要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader。架构
2.为何Dubbo本身实现SPIapp
JDK中SPI具备很大的缺点,JDK中标准的SPI会一次性实例化扩展点全部的实现,无论这些实例化出来的扩展点实现有没有被用到。有的扩展点实现初始化时很是的耗时,即便没有用到也会被加载,这样就很浪费资源。
Dubbo的SPI机制中增长了对扩展点IOC和AOP的支持,一个扩展点能够直接setter注入到其余的扩展点中。框架
3.Dubbo中扩展点的概念ide
Dubbo做用灵活的框架,并不会强制全部用户都必定使用Dubbo提供的某些架构。例如注册中心(Registry),Dubbo提供了zk和redis,可是若是咱们更倾向于其余的注册中心的话,咱们能够替换掉Dubbo提供的注册中心。针对这种可被替换的技术实现点咱们称之为扩展点,相似的扩展点有不少,例如Protocol,Filter,Loadbalance等等。函数
4.微内核架构工具
微内核架构 (Microkernel architecture) 模式也被称为插件架构 (Plugin architecture) 模式。本来与内核集成在一块儿的组件会被分离出来,内核提供了特定的接口使得这些组件能够灵活的接入,这些组件在内核的管理下工做,可是这些组件能够独立的发展、更改(不会对现有系统形成改动),只要符合内核的接口便可。典型的例子好比,Eclipse,IDEA 。学习
5.Dubbo 的微内核设计编码
Dubbo内核对外暴露出扩展点,经过扩展点能够实现定制的符合本身业务需求的功能。Dubbo内核经过ExtensionLoader扩展点加载器来加载各个SPI扩展点。Dubbo 内核对扩展是无感的 ,彻底不知道扩展的存在 ,内核代码中不会出现使用具体扩展的硬编码。
术语说明 :
SPI : Service Provider Interface 。
扩展点 : 称 Dubbo 中被 @SPI 注解的 Interface 为一个扩展点。
扩展 : 被 @SPI 注解的 Interface 的实现称为这个扩展点的一个扩展。
6.Dubbo中SPI的约定
扩展点约定 : 扩展点必须是 Interface 类型 ,必须被 @SPI 注解 ,知足这两点才是一个扩展点。
扩展定义约定:在 META-INF/services/$扩展点接口的全类名,META-INF/dubbo/$扩展点接口的全类名 , META-INF/dubbo/internal/$扩展点接口的全类名 , 这些路径下定义的文件名称为 $扩展点接口的全类名 , 文件中以键值对的方式配置扩展点的扩展实现。
默认适应扩展 :被 @SPI("abc") 注解的 Interface , 那么这个扩展点的缺省适应扩展就是 SPI 配置文件中 key 为 "abc" 的扩展。
7.接口实现类配置
经过注解@SPI("dubbo"),默认使用Protocol的实现类DubboProtocol,其实dubbo和实现类DubboProtocol关系相似Spring配置文件中的id和class的关系,不一样的是Dubbo的关系是
配置在目录/META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol文件中,文件内容为:
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
简单来讲其实现机制就相似Spring的bean注入,经过key(dubbo、http、hessian)来找到其实现类。
8.扩展加载器 ExtensionLoader
扩展加载器绝对是一个核心组件了 ,它控制着 dubbo 内部全部扩展点的初始化、加载扩展的过程。这个类的源码是颇有必要深刻学习的。从 Dubbo 内核设计简图能够看到,如今的学习尚未接触到 dubbo 的内核。
public class ExtensionLoader<T> { private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class); private static final String SERVICES_DIRECTORY = "META-INF/services/"; private static final String DUBBO_DIRECTORY = "META-INF/dubbo/"; private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/"; private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*"); private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>(); private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>(); // ============================== private final Class<?> type; private final ExtensionFactory objectFactory; private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>(); private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>(); private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>(); private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>(); private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>(); private volatile Class<?> cachedAdaptiveClass = null; private String cachedDefaultName; private volatile Throwable createAdaptiveInstanceError; private Set<Class<?>> cachedWrapperClasses; private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();
ExtensionLoader 中会存储两个静态属性 , EXTENSION_LOADERS 保存了内核开放的扩展点对应的 ExtensionLoader 实例对象 (说明了一种扩展点有一个对应的 ExtensionLoader 对象)。EXTENSION_INSTANCES 保存了扩展类型 (Class) 和扩展类型的实例对象。
type : 被 @SPI 注解的 Interface , 也就是扩展点。
objectFactory : 扩展工厂,能够从中获取到扩展类型实例对象 ,缺省为 AdaptiveExtensionFactory。
cachedNames : 保存不知足装饰模式(不存在只有一个参数,而且参数是扩展点类型实例对象的构造函数)的扩展的名称。
cachedClasses : 保存不知足装饰模式的扩展的 Class 实例 , 扩展的名称做为 key , Class 实例做为 value。
cachedActivates : 保存不知足装饰模式 , 被 @Activate 注解的扩展的 Class 实例。
cachedAdaptiveClass : 被 @Adpative 注解的扩展的 Class 实例 。
cachedInstances : 保存扩展的名称和实例对象 , 扩展名称为 key , 扩展实例为 value。
cachedDefaultName : 扩展点上 @SPI 注解指定的缺省适配扩展。
createAdaptiveInstanceError : 建立适配扩展实例过程当中抛出的异常。
cachedWrapperClasses : 知足装饰模式的扩展的 Class 实例。
exceptions : 保存在加载扩展点配置文件时,加载扩展点过程当中抛出的异常 , key 是当前读取的扩展点配置文件的一行 , value 是抛出的异常
9.SPI配置文件读取
咱们已经知道Dubbo将Protocol的实现类配置到/META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol配置文件中,接下来咱们要看的就是将配置文件中的对应关系解析出来了。其处理操做是在ExtensionLoder的loadFile方法中类来实现的,
简单来讲读取dubbo=com.alibaba.dubbo.registry.dubbo.DubboRegistryFactory,获取到键dubbo,初始化值com.alibaba.dubbo.registry.dubbo.DubboRegistryFactory而后将对应关系保存到一个Map中,这样就能够根据key找到实现类了。
private void loadFile(Map<String, Class<?>> extensionClasses, String dir) { String fileName = dir + type.getName(); try { Enumeration<java.net.URL> urls; ClassLoader classLoader = findClassLoader(); if (classLoader != null) { urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } if (urls != null) { while (urls.hasMoreElements()) { java.net.URL url = urls.nextElement(); try { BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8")); try { String line = null; while ((line = reader.readLine()) != null) { final int ci = line.indexOf('#'); if (ci >= 0) line = line.substring(0, ci); line = line.trim(); if (line.length() > 0) { try { String name = null; int i = line.indexOf('='); if (i > 0) { name = line.substring(0, i).trim(); line = line.substring(i + 1).trim(); } if (line.length() > 0) { Class<?> clazz = Class.forName(line, true, classLoader); 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."); } if (clazz.isAnnotationPresent(Adaptive.class)) { if (cachedAdaptiveClass == null) { cachedAdaptiveClass = clazz; } else if (!cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName()); } } else { try { clazz.getConstructor(type); Set<Class<?>> wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); wrappers = cachedWrapperClasses; } wrappers.add(clazz); } catch (NoSuchMethodException e) { clazz.getConstructor(); if (name == null || name.length() == 0) { name = findAnnotationName(clazz); if (name == null || name.length() == 0) { if (clazz.getSimpleName().length() > type.getSimpleName().length() && clazz.getSimpleName().endsWith(type.getSimpleName())) { name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase(); } else { throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url); } } } String[] names = NAME_SEPARATOR.split(name); if (names != null && names.length > 0) { Activate activate = clazz.getAnnotation(Activate.class); if (activate != null) { 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) { extensionClasses.put(n, clazz); } else if (c != clazz) { throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); } } } } } } } catch (Throwable t) { IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t); exceptions.put(line, e); } } } // end of while read lines } finally { reader.close(); } } catch (Throwable t) { logger.error("Exception when load extension class(interface: " + type + ", class file: " + url + ") in " + url, t); } } // end of while urls } } catch (Throwable t) { logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t); } }