前言

  阅读本文需要具备java spi的基础,本文不讲java spi,please google it.

 

一.Dubbo SPI 简介

  SPI(Service Provider Interface)是服务发现机制,Dubbo没有使用jdk SPI而对其增强和扩展:

* jdk SPI仅通过接口类名获取所有实现,但是Duboo SPI可以根据接口类名和key值获取具体一个实现
* 可以对扩展类实例的属性进行依赖注入,即IOC
* 可以采用装饰器模式实现AOP功能
  你可以发现Dubbo的源码中有很多地方都用到了@SPI注解,例如:Protocol(通信协议),LoadBalance(负载均衡)等。基于Dubbo
SPI,我们可以非常容易的进行拓展。ExtensionLoader是扩展点核心类,用于载入Dubbo中各种可配置的组件,比如刚刚说的Protocol和LoadBalance等。那么接下来我们看一下Dubbo
SPI的示例

 

二.Dubbo SPI 示例

  比如现在我们要拓展Protocol这个组件,新建一个DefineProtocol类并修改默认端口为8888:
1 /** 2 * @author GrimMjx 3 */ 4 public class DefineProtocol implements
Protocol { 5 @Override 6 public int getDefaultPort() { 7 return 8888; 8 }
9 10 @Override 11 public <T> Exporter<T> export(Invoker<T> invoker) throws
RpcException {12 return null; 13 } 14 15 @Override 16 public <T> Invoker<T>
refer(Class<T> aClass, URL url)throws RpcException { 17 return null; 18 } 19 20
@Override21 public void destroy() { 22 23 } 24 }
  配置文件的文件名字是接口的全限定名,那么在这个例子中就是:com.alibaba.dubbo.rpc.Protocol

  Dubbo SPI所需的配置文件要放在以下3个目录任意一个中:

  META-INF/services/

  META-INF/dubbo/

  META-INF/dubbo/internal/



  同时需要将服务提供者配置文件设计成KV键值对的形式,Key是拓展类的name,Value是扩展的全限定名实现类。比如:
myProtocol=com.grimmjx.edu.DefineProtocol
  然后测试一下:
ClassPathXmlApplicationContext context = new
ClassPathXmlApplicationContext("dubbo-client.xml"); Protocol myProtocol =
ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myProtocol");
System.out.println(myProtocol.getDefaultPort());
  结果如下:



 

三.源码解析

  那我们就从上面的方法看起,重要方法红色标注:
ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myProtocol");

1.getExtensionLoader方法,入参是一个可拓展的借口,返回ExtensionLoader实体类,然后可以通过name(key)来获取具体的扩展:
1 public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { 2
if (type == null) { 3 throw new IllegalArgumentException("Extension type ==
null"); 4 } 5 // 扩展点必须是接口 6 if (!type.isInterface()) { 7 throw new
IllegalArgumentException("Extension type (" + type + ") is not an interface!");
8 } 9 // 必须有@SPI注解 10 if (!withExtensionAnnotation(type)) { 11 throw new
IllegalArgumentException("Extension type (" + type +12 ") is not an extension,
because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); 13 } 14
// 每个扩展只会被加载一次 15 ExtensionLoader<T> loader = (ExtensionLoader<T>)
EXTENSION_LOADERS.get(type);16 if (loader == null) { 17 // 初始化扩展 18
EXTENSION_LOADERS.putIfAbsent(type,new ExtensionLoader<T>(type)); 19 loader =
(ExtensionLoader<T>) EXTENSION_LOADERS.get(type); 20 } 21 return loader; 22 }
2.getExtension方法,首先检查缓存,如果没有则用双检锁方式创建实例:
1 public T getExtension(String name) { 2 if (StringUtils.isEmpty(name)) { 3
throw new IllegalArgumentException("Extension name == null"); 4 } 5 if
("true".equals(name)) { 6 // 默认拓展实现类 7 return getDefaultExtension(); 8 } 9
// 获取持有目标对象 10 Holder<Object> holder = getOrCreateHolder(name); 11 Object
instance = holder.get(); 12 // 双检锁 13 if (instance == null) { 14 synchronized
(holder) {15 instance = holder.get(); 16 if (instance == null) { 17 // 创建实例 18
instance = createExtension(name); 19 holder.set(instance); 20 } 21 } 22 } 23
return (T) instance; 24 }
3.createExtension方法,这个方法比较核心。做了有4件事情,第3件和第4件分别为上面介绍Dubbo SPI中对jdk
SPI扩展的第二和第三点(红字已标注)。请看代码注释:
1 private T createExtension(String name) { 2 //
1.加载配置文件所有拓展类,得到配置名-拓展类的map,从map中获取到拓展类 3 Class<?> clazz =
getExtensionClasses().get(name); 4 if (clazz == null) { 5 throw
findException(name); 6 } 7 try { 8 T instance = (T)
EXTENSION_INSTANCES.get(clazz); 9 if (instance == null) { 10 // 2.通过反射创建实例 11 //
EXTENSION_INSTANCES这个map是配置名-拓展类实例的map 12
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());13 instance = (T)
EXTENSION_INSTANCES.get(clazz);14 } 15 // 3.注入依赖,即IOC 16
injectExtension(instance);17 Set<Class<?>> wrapperClasses =
cachedWrapperClasses;18 if (CollectionUtils.isNotEmpty(wrapperClasses)) { 19 //
4.循环创建Wrapper实例 20 for (Class<?> wrapperClass : wrapperClasses) { 21 //
通过反射创建Wrapper实例22 // 向Wrapper实例注入依赖,最后赋值给instance 23 // 自动包装实现类似aop功能 24
instance = injectExtension((T)
wrapperClass.getConstructor(type).newInstance(instance));25 } 26 } 27 return
instance;28 } catch (Throwable t) { 29 throw new
IllegalStateException("Extension instance (name: " + name + ", class: " +30
type + ") couldn't be instantiated: " + t.getMessage(), t); 31 } 32 }
4.getExtensionClasses方法,这里就是找出所有拓展类,返回一个配置名-拓展类的map。
1 private Map<String, Class<?>> getExtensionClasses() { 2 Map<String,
Class<?>> classes = cachedClasses.get(); 3 // 双检锁 4 if (classes == null) { 5
synchronized (cachedClasses) { 6 classes = cachedClasses.get(); 7 if (classes
==null) { 8 // 缓存无则加载 9 classes = loadExtensionClasses(); 10
cachedClasses.set(classes);11 } 12 } 13 } 14 return classes; 15 }
5.loadExtensionClasses方法,主要就是解析SPI注解,然后加载指定目录的配置文件,也不是很难
1 private Map<String, Class<?>> loadExtensionClasses() { 2 // 获取SPI注解,检查合法等
3 final SPI defaultAnnotation = type.getAnnotation(SPI.class); 4 if
(defaultAnnotation !=null) { 5 String value = defaultAnnotation.value(); 6 if
(value !=null && (value = value.trim()).length() > 0) { 7 String[] names =
NAME_SEPARATOR.split(value); 8 if(names.length > 1) { 9 throw new
IllegalStateException("more than 1 default extension name on extension " +
type.getName()10 + ": " + Arrays.toString(names)); 11 } 12 if(names.length ==
1) cachedDefaultName = names[0]; 13 } 14 } 15 16 Map<String, Class<?>>
extensionClasses =new HashMap<String, Class<?>>(); 17 //
META-INF/dubbo/internal/目录 18 loadFile(extensionClasses,
DUBBO_INTERNAL_DIRECTORY);19 // META-INF/dubbo/目录 20
loadFile(extensionClasses, DUBBO_DIRECTORY);21 // META-INF/services/目录 22
loadFile(extensionClasses, SERVICES_DIRECTORY);23 return extensionClasses; 24 }

  这里返回的extensionClasses的map就肯定包含了"myProtocol"->"com.grimmjx.edu.DefineProtocol"。同时也可以看到,dubbo支持有很多协议:



  接下来不用多说了吧,再从map里get出"myProtocol",得到的就是我们自定义的协议类。

  
上面3.createExtension方法,这个方法里注释的3和4很关键,里面实现了依赖注入和AOP的功能,那么接下来我们主要看看Dubbo的IOC和AOP

 

Dubbo IOC

   Dubbo
IOC是通过setter方法注入依赖的,首先遍历方法是否有setter方法特征,如果有则通过objectFactory获取依赖对象进行注入。Dubbo注入的可能是Dubbo的扩展,也有可能是一个Spring
bean!

  上面的3方法中有这一行代码,实现了Dubbo SPI的IOC
injectExtension(instance);
  1.injectExtension方法。我们主要看这个方法,自动装配的功能都在这个方法中:
1 private T injectExtension(T instance) { 2 try { 3 if (objectFactory !=
null) { 4 for (Method method : instance.getClass().getMethods()) { 5 //
如果方法以set开头 && 只有一个参数 && 方法是public级别的 6 if (method.getName().startsWith("set")
7 && method.getParameterTypes().length == 1 8 &&
Modifier.isPublic(method.getModifiers())) { 9 // 获取setter方法参数类型 10 Class<?> pt
= method.getParameterTypes()[0]; 11 try { 12 String property =
method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase()
+ method.getName().substring(4) : ""; 13 14 // 关键,从objectFactory里拿出依赖对象 15
Object object = objectFactory.getExtension(pt, property); 16 if (object != null
) {17 // 利用反射进行注入 18 method.invoke(instance, object); 19 } 20 } catch
(Exception e) {21 logger.error("fail to inject via method " + method.getName()
22 + " of interface " + type.getName() + ": " + e.getMessage(), e); 23 } 24 }
25 } 26 } 27 } catch (Exception e) { 28 logger.error(e.getMessage(), e); 29
}30 return instance; 31 }
  2.objectFactory,究竟这里的objectFactory是什么呢?它是ExtensionFactory类型的,自身也是一个扩展点。
我先告诉你这里的objectFactory是AdaptiveExtensionFactory。后面会有解释。
1 private ExtensionLoader(Class<?> type) { 2 this.type = type; 3 objectFactory
= (type == ExtensionFactory.class ? null :
ExtensionLoader.getExtensionLoader(ExtensionFactory.class
).getAdaptiveExtension());4 }



  3.getExtension方法,这里比较简单,为了直观,用debug模式看一下,factories有两个,分别是SpiExtensionFactory和SpringExtensionFactory



  对于SpringExtensionFactory就是从工厂里根据beanName拿到Spring
bean来注入,对于SpiExtensionFactory就是根据传入Class获取自适应拓展类,那么我们写一段代码,来试试获取一个Spring
Bean玩玩,先定义一个bean:
1 /** 2 * @author GrimMjx 3 */ 4 public class Worker { 5 6 private int
age = 24; 7 8 public int getAge() { 9 return age; 10 } 11 12 public void
setAge(int age) { 13 this.age = age; 14 } 15 }
  然后再配置文件里配置这个Spring Bean:



  最后简单写个Main方法,可以看出SpringExtensionFactory可以加载Spring Bean:



 

Dubbo AOP

  Dubbo中也支持Spring AOP类似功能,通过装饰者模式,使用包装类包装原始的扩展点实例。
在扩展点实现前后插入其他逻辑,实现AOP功能。说这很绕口啊,那什么是包装类呢?举个例子你就知道了:
1 class A{ 2 private A a; 3 public A(A a){ 4 this.a = a; 5 } 6 7 public
void do(){ 8 // 插入扩展逻辑 9 a.do(); 10 } 11 }

  这里的插入扩展逻辑,是不是就是实现了AOP功能呢?比如说Protocol类,有2个Wrapper,分别是ProtocolFilterWrapper和ProtocolListenerWrapper,我们可以在dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol看到:
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper listener
=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
  源码的话createExtension方法里的注释已经写的很清楚了,这里可以自行研究。所以我们在最开始的Dubbo
SPI的例子中,我们打个断点就很明显了,得到的myProtocol对象其实是这样的:



  
如果你调用export方法的话,会先经历ProtocolFilterWrapper的export方法,再经历ProtocolListenerWrapper的export方法
,这样是不是就实现了Spring AOP的功能呢?

 

Dubbo SPI 自适应

  这里getAdaptiveExtension到底获取的是什么呢,这里涉及到SPI 自适应扩展,十分重要,涉及到@Adaptive注解。


  如果注解加在类上,比如说com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory(自行验证):直接加载当前的适配器


  如果注解加载方法上,比如说com.alibaba.dubbo.rpc.Protocol:动态创建一个自适应的适配器,就像是执行如下代码,返回的是一个动态生成的代理类
Protocol adaptiveExtension = ExtensionLoader.getExtensionLoader(Protocol.class
).getAdaptiveExtension(); 1 public class Protocol$Adpative implements
com.alibaba.dubbo.rpc.Protocol { 2 public void destroy() { 3 throw new
UnsupportedOperationException("method public abstract void
com.alibaba.dubbo.rpc.Protocol.destroy() of interface
com.alibaba.dubbo.rpc.Protocol is not adaptive method!"); 4 } 5 6 public int
getDefaultPort() { 7 throw new UnsupportedOperationException("method public
abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface
com.alibaba.dubbo.rpc.Protocol is not adaptive method!"); 8 } 9 10 public
com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) {11 if
(arg0 ==null) throw new
IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null"); 12
if (arg0.getUrl() == null) 13 throw new
IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() ==
null"); 14 com.alibaba.dubbo.common.URL url = arg0.getUrl(); 15 String extName
= (url.getProtocol() ==null ? "dubbo" : url.getProtocol()); 16 if (extName ==
null) 17 throw new IllegalStateException("Fail to get
extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ")
use keys([protocol])"); 18 com.alibaba.dubbo.rpc.Protocol extension =
(com.alibaba.dubbo.rpc.Protocol)
ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
19 return extension.export(arg0); 20 } 21 22 public
com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0,
com.alibaba.dubbo.common.URL arg1) {23 if (arg1 == null) throw new
IllegalArgumentException("url == null"); 24 com.alibaba.dubbo.common.URL url =
arg1;25 String extName = (url.getProtocol() == null ? "dubbo" :
url.getProtocol());26 if (extName == null) 27 throw new
IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol)
name from url(" + url.toString() + ") use keys([protocol])"); 28
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)
ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
29 return extension.refer(arg0, arg1); 30 } 31 }

  为什么getDefaultPort和destroy方法都是直接抛出异常呢?因为Protocol接口只有export和refer方法使用了@Adaptive注解,Dubbo会自动生成自适应实例,其他方法都是抛异常。

  为什么还要要动态生成呢?有时候拓展不像在框架启动的时候被加载,而是希望在扩展方法被调用的时候,根据运行时参数进行加载。

  最后看看上面2个标红的代码,是不是就是本文开始的源码分析,是不是很简单了?

 

 

 

 

 参考

http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html
<http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html>(官方文档,稳稳的)

https://blog.51cto.com/13679539/2125211
<https://blog.51cto.com/13679539/2125211>

友情链接
ioDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信