欢迎来到我的博客,代码的世界里,每一行都是一个故事
前言
Dubbo作为一款流行的分布式服务框架,其强大的扩展机制让它成为众多Java应用的首选。但是,这其中的秘密究竟是什么?本文将带你穿越Dubbo的魔法之门,深入研究SPI机制,探索其神奇的工作方式,以及如何在你的Dubbo项目中利用它来实现更灵活的架构。
Dubbo中的SPI
Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。
Dubbo的SPI机制包括以下关键点:
- 扩展点接口:
- 扩展点接口是定义扩展点功能的接口,通常由Dubbo提供。例如,
com.alibaba.dubbo.rpc.Protocol
是Dubbo中的一个扩展点接口,用于定义协议的实现。
- 使用
@SPI
注解:
- 扩展点接口通常会使用
@SPI
注解来标识默认的实现类。这个注解用于标记一个接口,并在括号中指定默认的实现类的名字。 - 例如,在
com.alibaba.dubbo.rpc.Protocol
接口上可能会有以下注解:
@SPI("dubbo") public interface Protocol { // ... }
- 这表示
Protocol
接口的默认实现类是dubbo
。 - 配置文件:
- Dubbo的SPI配置文件仍然位于
META-INF/dubbo/
目录下,但它的作用不同于Java标准的SPI配置文件。Dubbo的SPI配置文件主要用于指定各个扩展点的实现类的别名,而不是具体的实现类。 - 例如,Dubbo的SPI配置文件中可能包含以下内容:
dubbo=com.alibaba.dubbo.rpc.protocol.DubboProtocol hessian=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
- 这些配置将不同的实现类与别名关联起来。
- 使用扩展点:
- 在Dubbo的XML配置文件中,你可以使用扩展点的别名来指定使用哪个实现类。这个别名通常与Dubbo SPI配置文件中的配置相对应。
- 例如:
<dubbo:protocol name="dubbo" />
- 这表示使用了
dubbo
别名对应的com.alibaba.dubbo.rpc.protocol.DubboProtocol
实现类。
在Dubbo中,需要使用@SPI
注解来标识扩展点接口的默认实现类,并且SPI配置文件的作用是指定别名与具体实现类的映射关系。这个机制允许你在配置中轻松切换不同的实现类,而不需要直接在XML配置中写全限定类名。再次为之前的回答的不准确信息道歉。
dubbo SPI的优点
- 能够实现按需加载,JDK SPI仅仅通过接口类名获取所有实现,在通过迭代器获取指定实现,而ExtensionLoader则通过接口类名和key值获取一个实现
- 支持AOP(将实现类包装在Wrapper中,Wrapper中实现公共增强逻辑)
- 支持IOC(能够通过set方法注入其他扩展点)
源码分析
下面我们从 ExtensionLoader 的 getExtension 方法作为入口,对拓展类对象的获取过程进行详细的分析。
public T getExtension(String name) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name == null"); } if ("true".equals(name)) { // 获取默认的拓展实现类 return getDefaultExtension(); } // Holder,顾名思义,用于持有目标对象 Holder<Object> holder = getOrCreateHolder(name); Object instance = holder.get(); // 双重检查 if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { // 创建拓展实例 instance = createExtension(name); // 设置实例到 holder 中 holder.set(instance); } } } return (T) instance; }
上面代码的逻辑比较简单,首先检查缓存,缓存未命中则创建拓展对象。下面我们来看一下创建拓展对象的过程是怎样的。
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); } // 向实例中注入依赖 injectExtension(instance); Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (CollectionUtils.isNotEmpty(wrapperClasses)) { // 循环创建 Wrapper 实例 for (Class<?> wrapperClass : wrapperClasses) { // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。 // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量 instance = injectExtension( (T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException("..."); } }
createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:
- 通过 getExtensionClasses 获取所有的拓展类
- 通过反射创建拓展对象
- 向拓展对象中注入依赖
- 将拓展对象包裹在相应的 Wrapper 对象中
以上步骤中,第一个步骤是加载拓展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。由于此类设计源码较多,这里简单的总结下ExtensionLoader整个执行逻辑:
getExtension(String name) #根据key获取拓展对象 -->createExtension(String name) #创建拓展实例 -->getExtensionClasses #根据路径获取所有的拓展类 -->loadExtensionClasses #加载拓展类 -->cacheDefaultExtensionName #解析@SPI注解 -->loadDirectory #方法加载指定文件夹配置文件 -->loadResource #加载资源 -->loadClass #加载类,并通过 loadClass 方法对类进行缓存