【Dubbo】SPI 机制原理分析 --静态扩展点
在了解 Dubbo 的 SPI 之前 先来了解一下 JAVA 自带的 SPI。SPI 是 JDK 内置的一种服 务提供发现机制。目前市面上有很多框架都是用它来做服务的扩展发现。简单来说,它是一种动态替换发现的机制。
举个简单的例子,我们想在运行时动态给它添加实现,只需要添加一个实现,然后把新的实现描述给 JDK 知道就行了。比如JDBC、日志框架都有用到。
实现 SPI 需要遵循的标准:
- 需要在 classpath 下创建一个目录,该目录命名必须是:META-INF/service
在该目录下创建一个 properties 文件,该文件需要满足以下几个条件
- 文件名必须是扩展的接口的全路径名称
- 文件内部描述的是该扩展接口的所有实现类
- 文件的编码格式是 UTF-8
- 通过 java.util.ServiceLoader 的加载机制来发现
关于 Java SPI 的详解及示例可以参考我的这篇文章…
- 三个重要概念:扩展点:一个Java接口 ; 扩展:扩展点的实现类 ;扩展实例:扩展点实现类的实例
- SPI机制实际上就是“基于接口的编程+策略模式+配置文件”组合实现的一种动态加载机制
Dubbo 的 SPI 扩展机制在 JAVA 自带的 SPI 基础上加入了扩展点的名称,即每个实现类都会对应至一个扩展点名称,其目的是应用可基于此名称进行相应的装配(因为一个服务可能会有多种实现)。
扩展点要加@SPI注解
- 是由 Dubbo 提供的注解
- @SPI(“value”)可以指定扩展点的默认实现
需要在 resource 目录下配置 META-INF/dubbo 或者 META-INF/dubbo/internal 或者 META-INF/services目录下,并基于 SPI 接口去创建一个文件
- 文件名称和接口名称保持一致
- 文件内容和 SPI 有差异,内容是 KEY 对应 Value ,key也可以理解成扩展的别名
ExtensionLoader
- 负责扩展的加载和生命周期维护
- 类似于 Java SPI 的 ServiceLoader
Dubbo 针对的扩展点非常多,可以针对协议、拦截、集群、路由、负载均衡、序列化、容器… 几乎里面用到的所有功能,都可以实现自己的扩展,这个是 dubbo 比较强大的一点。
我们这篇就来看看 Dubbo 静态扩展点…
所谓静态扩展点就和静态代理很类似,即直接通过 name 去加载相应的扩展实例。
1.代码示例
示例:自定义协议 MyProtocol,然后配置到 dubbo.protocol。实现步骤如下:
1)自定义 MyProtocol 实现 Dubbo 的 Protocol 接口
public class MyProtocol implements Protocol {
@Override
public int getDefaultPort() {
return 8080;
}
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
return null;
}
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
return null;
}
@Override
public void destroy() {
}
}
注意,我们上面不是说了 Dubbo 中,所有的扩展点都要有 @SPI 注解吗,那 Protocol 接口有吗?我们打开看看:
可以看到 Protocol 确实是 Dubbo 中内置的一个扩展点,并且指定了默认采用 dubbo 协议。
2)创建 META-INF/dubbo目录,并创建我们自己的 org.apache.dubbo.rpc.Protocol 文件
3)通过 ExtensionLoader 去获取自定义协议对象
// ExtensionLoader 拿到具体实现类对象
Protocol p = ExtensionLoader.getExtensionLoader(Protocol.class) // 根据Protocol.class找到对应文件
.getExtension("myProtocol"); // 根据key myProtocol找到对应类
// 调用具体实现
System.out.print(protocol.getDefaultPort)
可以看到运行结果,是执行的自定义的协议
4)配置协议为 myProtocol
<dubbo:protocol name="myProtocol"/>
2.源码分析
所谓的扩展,就是通过指定目录下配置一个对应接口的实现类,然后程序会进行查找和解析。那么这里就涉及两个问题:
- 怎么解析?
- 被加载的类的实例对象如何存储和使用?
我们下面就从ExtensionLoader.getExtensionLoader.getExtension
着手,看到它底做了什么事情,能实现扩展协议的查找和加载。
getExtensionLoader()
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
// 如果参数为空,报异常
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
// 如果不是个接口,报异常
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
// 如果扩展点(接口)没有@SPI注解(上面Protocol是有的),报异常
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type
+") is not an extension, because it is NOT annotated with @"
+ SPI.class.getSimpleName() + "!");
}
// 初始化ExtensionLoader
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
ExtensionLoader()
实例化ExtensionLoader,传入接口的class对象
private ExtensionLoader(Class<?> type) {
this.type = type;
// 如果当前的 type=ExtensionFactory,type,那么 objectFactory=null
objectFactory = (type == ExtensionFactory.class ? // 否则会创建一个自适应扩展点给到 objectFacotry
null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
注:objectFacotry 在自适应扩展点中分析
getExtension()
根据扩展的 key(别名),获取扩展实例
// name是当前扩展点下扩展的key
// 这里name就是myprotocol,而返回的实现类应该就是 MyProtocol
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
// name=true,返回一个默认扩展
if ("true".equals(name)) {
return getDefaultExtension();
}
Holder<Object> holder = getOrCreateHolder(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;
}
createExtension()
创建扩展实例
private T createExtension(String name) {
// 加载指定路径下的所有文件,获取其中配置的k,v信息
Class<?> clazz = getExtensionClasses().get(name);
// 如果传入的name(myProtocol)配置的全类名(com...MyProtocol)没找到class,抛异常
if (clazz == null) {
throw findException(name);
}
try {
// EXTENSION_INSTANCES是一个concurrentHashMap,用来缓存class,及其实例对象
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)) {
for (Class<?> wrapperClass : wrapperClasses) {
// 创建指定name的实例,并进行依赖注入(该实例对象的某个成员属性也是扩展点)
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: "
+ type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
getExtensionClasses()
加载当前扩展点的所有扩展,保存在一个hashMap中
- 查找指定目录 /META-INF/dubbo 和 /META-INF/services 下指定的文件(文件名为接口全类名)
扫描这个文件下的所有配置信息。然后保存到一个 HashMap 中(classes)
- key=扩展别名(key); myprotocol
- value=对应配置的类的实例;MyProtocol.class
private Map
> getExtensionClasses() { Map<String, Class<?>> classes = cachedClasses.get();
// 如果 cachedApdaptiveClas!=null ,直接返回这个 cachedAdaptiveClass
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses(); // 具体加载过程
cachedClasses.set(classes);
}
}
}
return classes;
}
cachedAdaptiveClass 是一个类级别自适应扩展点,表示告诉 dubbo spi loader,“我是一个自适应扩展点,来加载我吧” ,具体情况会在自适应扩展点中分析
injectExtension()
这个方法是用来实现依赖注入的,如果被加载的实例中,有成员属性本身也是一个扩展点,则会通过 set 方法进行注入
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
// objectFactory这里用到了
// 获得实例对应的方法,判断方法是否是一个set方法
for (Method method : instance.getClass().getMethods()) {
if (isSetter(method)) {
// 可以选择禁用依赖注入
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
// 获得方法的参数,这个参数必须是一个对象类型并且是一个扩展点
Class<?> pt = method.getParameterTypes()[0]; // 获得这个方法的参数类型
if (ReflectUtils.isPrimitives(pt)) {
// 如果不是对象类型,就跳过
continue;
}
try {
// 获得这个方法的属性名称
String property = getSetterProperty(method);
// 根据class以及name,使用自适应扩展点进行加载,并赋值到当前set方法中
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// 调用set方法进行赋值
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
所谓的扩展点,套路都一样,不管是 springfactorieyLoader,还是 Dubbo 的 spi。实际上,Dubbo 的功能会更加强大,比如自适应扩展点,比如依赖注入。
关于自适应扩展点和激活扩展点的分析请看下一篇文章…
还没有评论,来说两句吧...