Dubbo源码分析之SPI扩展机制

1. jdk spi扩展机制

1.1 spi是什么?

spiservice provider interface,是JDK内置的一种服务提供发现机制。它是针对厂商或插件来进行服务扩展发现的,像JDBC、日志框架等都有用的。简单来说,它是一种动态替换发现的机制。举个简单的例子,如果我们定义了一个规范,需要第三方厂商去实现,那么对于我们应用方来说,只需要集成对应厂商的插件,既可以完成对应规范的实现机制。 形成一种插拔式的扩展手段。

1.2 jdk spi规范

实现SPI需要遵守SPI本身的规范:

  • 服务的提供者需要在classpath下创建一个目录,该目录命名必须是:META-INF/services

  • 在该目录下创建一个properties文件,该文件需要满足以下几个条件:

    • 文件名必须是扩展的接口的全路径名称

    • 文件内部描述的是该扩展接口的所有实现类

    • a) 文件的编码格式是UTF-8

  • 通过java.util.ServiceLoader的加载机制来发现

SPI应用有很多,比如常用的common-logging 日志门面接口,还有java.sql.Driver驱动类,需要第三方厂商去实现接口。

 

2. dubbo spi 扩展机制

想要弄懂dubbo,理解dubbo的扩展机制是必须的。在dubbo中有许多扩展点,比如有:协议扩展点Portocol、拦截器扩展点Filter、动态代理扩展点ProxyFactory、注册中心扩展点RegistryFactory、负载均衡扩展点LoadBance等等。dubbo为了应对各种场景,它的内部组件都是通过spi扩展机制进行管理的。

2.1 dubbo spi扩展约定

Dubbo默认依次扫描META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/三个classpath目录下的配置文件。配置文件内容为:扩展名=扩展实现类全限定名,以k=v形式。

2.2 扩展点加载类ExtentionLoader

扩展加载器绝对是一个核心组件了 ,它控制着 dubbo 内部所有扩展点的初始化、加载扩展的过程。

  1:private static final String SERVICES_DIRECTORY = "META-INF/services/";
  2: 
  3: private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
  4: 
  5: private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
  6: 
  7: private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
  8: 
  9: // ============================== 静态属性 ==============================
 10: 
 11: /**
 12:  * 拓展加载器集合
 13:  *
 14:  * key:拓展接口
 15:  */
 16: private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
 17: /**
 18:  * 拓展实现类集合
 19:  *
 20:  * key:拓展实现类
 21:  * value:拓展对象。
 22:  *
 23:  * 例如,key 为 Class<AccessLogFilter>
 24:  *  value 为 AccessLogFilter 对象
 25:  */
 26: private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
 27: 
 28: // ============================== 对象属性 ==============================
 29: 
 30: /**
 31:  * 拓展接口。
 32:  * 例如,Protocol
 33:  */
 34: private final Class<?> type;
 35: /**
 36:  * 对象工厂
 37:  *
 38:  * 用于调用 {@link #injectExtension(Object)} 方法,向拓展对象注入依赖属性。
 39:  *
 40:  * 例如,StubProxyFactoryWrapper 中有 `Protocol protocol` 属性。
 41:  */
 42: private final ExtensionFactory objectFactory;
 43: /**
 44:  * 缓存的拓展名与拓展类的映射。
 45:  *
 46:  * 和 {@link #cachedClasses} 的 KV 对调。
 47:  *
 48:  * 通过 {@link #loadExtensionClasses} 加载
 49:  */
 50: private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
 51: /**
 52:  * 缓存的拓展实现类集合。
 53:  *
 54:  * 不包含如下两种类型:
 55:  *  1. 自适应拓展实现类。例如 AdaptiveExtensionFactory
 56:  *  2. 带唯一参数为拓展接口的构造方法的实现类,或者说拓展 Wrapper 实现类。例如,ProtocolFilterWrapper 。
 57:  *   拓展 Wrapper 实现类,会添加到 {@link #cachedWrapperClasses} 中
 58:  *
 59:  * 通过 {@link #loadExtensionClasses} 加载
 60:  */
 61: private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
 62: 
 63: /**
 64:  * 拓展名与 @Activate 的映射
 65:  *
 66:  * 例如,AccessLogFilter。
 67:  *
 68:  * 用于 {@link #getActivateExtension(URL, String)}
 69:  */
 70: private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();
 71: /**
 72:  * 缓存的拓展对象集合
 73:  *
 74:  * key:拓展名
 75:  * value:拓展对象
 76:  *
 77:  * 例如,Protocol 拓展
 78:  *      key:dubbo value:DubboProtocol
 79:  *      key:injvm value:InjvmProtocol
 80:  *
 81:  * 通过 {@link #loadExtensionClasses} 加载
 82:  */
 83: private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
 84: /**
 85:  * 缓存的自适应( Adaptive )拓展对象
 86:  */
 87: private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
 88: /**
 89:  * 缓存的自适应拓展对象的类
 90:  *
 91:  * {@link #getAdaptiveExtensionClass()}
 92:  */
 93: private volatile Class<?> cachedAdaptiveClass = null;
 94: /**
 95:  * 缓存的默认拓展名
 96:  *
 97:  * 通过 {@link SPI} 注解获得
 98:  */
 99: private String cachedDefaultName;
100: /**
101:  * 创建 {@link #cachedAdaptiveInstance} 时发生的异常。
102:  *
103:  * 发生异常后,不再创建,参见 {@link #createAdaptiveExtension()}
104:  */
105: private volatile Throwable createAdaptiveInstanceError;
106: 
107: /**
108:  * 拓展 Wrapper 实现类集合
109:  *
110:  * 带唯一参数为拓展接口的构造方法的实现类
111:  *
112:  * 通过 {@link #loadExtensionClasses} 加载
113:  */
114: private Set<Class<?>> cachedWrapperClasses;
115: 
116: /**
117:  * 拓展名 与 加载对应拓展类发生的异常 的 映射
118:  *
119:  * key:拓展名
120:  * value:异常
121:  *
122:  * 在 {@link #loadFile(Map, String)} 时,记录
123:  */
124: private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();

@SPI 、@Adaptive 、@Activate 作用
@SPI (注解在类上) :  @SPI 注解标识了接口是一个扩展点 , 属性 value 用来指定默认适配扩展点的名称。

@Activate (注解在类型和方法上) :  @Activate 注解在扩展点的实现类上 ,表示了一个扩展类被获取到的的条件,符合条件就被获取,不符合条件就不获取 ,根据 @Activate 中的 group 、 value 属性来过滤 。具体参考 ExtensionLoader 中的  getActivateExtension 函数。

@Adaptive (注解在类型和方法上) :  @Adaptive 注解在类上 , 这个类就是缺省的适配扩展。@Adaptive 注解在扩展点 Interface 的方法上时 , dubbo 动态的生成一个这个扩展点的适配扩展类(生成代码 ,动态编译实例化 Class ),名称为 扩展点 Interface 的简单类名 + $Adaptive ,例如 : ProxyFactory$Adpative  。这么做的目的是为了在运行时去适配不同的扩展实例 , 在运行时通过传入的 URL 类型的参数或者内部含有获取 URL 方法的参数 ,从 URL 中获取到要使用的扩展类的名称 ,再去根据名称加载对应的扩展实例 ,用这个扩展实例对象调用相同的方法  。如果运行时没有适配到运行的扩展实例 , 那么就使用 @SPI 注解缺省指定的扩展。通过这种方式就实现了运行时去适配到对应的扩展。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值