Dubbo源码学习--Dubbo服务提供接口SPI机制

亦凉 2022-06-09 00:55 335阅读 0赞

Dubbo采用微内核+插件体系,使得设计优雅,扩展性强。那所谓的微内核+插件体系是如何实现的呢!大家是否熟悉spi(service providerinterface)机制,即我们定义了服务接口标准,让厂商去实现(如果不了解spi的请谷歌百度下), jdk通过ServiceLoader类实现spi机制的服务查找功能。可以参考博客 Java spi机制浅谈 接下来我们来了解一下Dubbo是如何实现SPI机制,首先SPI机制出现的需求场景应该是对同一个接口会有不同的实现类,我们可以根据应用场景通过配置来选择使用不同的实现类。

1、@SPI注解

Dubbo提供了一个@SPI注解

  1. public @interface SPI {
  2. String value() default ""; //指定默认的扩展点
  3. }

注解@SPI的值为接口实现类的标识
用法为注解到接口类上,表示默认使用接口的一个实现类
通过注解@SPI(“dubbo”),默认使用RegistryFactory的实现类DubboRegistryFactory

  1. @SPI("dubbo")
  2. public interface Protocol {
  3. /**
  4. * 获取缺省端口,当用户没有配置端口时使用。
  5. *
  6. * @return 缺省端口
  7. */
  8. int getDefaultPort();
  9. /**
  10. * 暴露远程服务:<br>
  11. * 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();<br>
  12. * 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。<br>
  13. * 3. export()传入的Invoker由框架实现并传入,协议不需要关心。<br>
  14. *
  15. * @param <T> 服务的类型
  16. * @param invoker 服务的执行体
  17. * @return exporter 暴露服务的引用,用于取消暴露
  18. * @throws RpcException 当暴露服务出错时抛出,比如端口已占用
  19. */
  20. @Adaptive
  21. <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
  22. /**
  23. * 引用远程服务:<br>
  24. * 1. 当用户调用refer()所返回的Invoker对象的invoke()方法时,协议需相应执行同URL远端export()传入的Invoker对象的invoke()方法。<br>
  25. * 2. refer()返回的Invoker由协议实现,协议通常需要在此Invoker中发送远程请求。<br>
  26. * 3. 当url中有设置check=false时,连接失败不能抛出异常,并内部自动恢复。<br>
  27. *
  28. * @param <T> 服务的类型
  29. * @param type 服务的类型
  30. * @param url 远程服务的URL地址
  31. * @return invoker 服务的本地代理
  32. * @throws RpcException 当连接服务提供方失败时抛出
  33. */
  34. @Adaptive
  35. <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
  36. /**
  37. * 释放协议:<br>
  38. * 1. 取消该协议所有已经暴露和引用的服务。<br>
  39. * 2. 释放协议所占用的所有资源,比如连接和端口。<br>
  40. * 3. 协议在释放后,依然能暴露和引用新的服务。<br>
  41. */
  42. void destroy();
  43. }
2、接口实现类配置

通过注解@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)来找到其实现类。

3、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找到实现类了。

  1. private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
  2. //获取配置文件路径/META_INF/dubbo/internal/com.alibaba.dubbo.registry.RegistryFactory
  3. String fileName = dir + type.getName();
  4. try {
  5. Enumeration<java.net.URL> urls;
  6. ClassLoader classLoader = findClassLoader();
  7. if (classLoader != null) {
  8. urls = classLoader.getResources(fileName);
  9. } else {
  10. urls = ClassLoader.getSystemResources(fileName);
  11. }
  12. if (urls != null) {
  13. while (urls.hasMoreElements()) {
  14. java.net.URL url = urls.nextElement();
  15. try {
  16. BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
  17. try {
  18. String line = null;
  19. while ((line = reader.readLine()) != null) {
  20. final int ci = line.indexOf('#');
  21. if (ci >= 0) line = line.substring(0, ci);
  22. line = line.trim();
  23. if (line.length() > 0) {
  24. try {
  25. String name = null;
  26. int i = line.indexOf('=');
  27. if (i > 0) {
  28. //获取key
  29. name = line.substring(0, i).trim();
  30. //获取value为类的完整路径
  31. line = line.substring(i + 1).trim();
  32. }
  33. if (line.length() > 0) {
  34. //初始化类
  35. Class<?> clazz = Class.forName(line, true, classLoader);
  36. if (! type.isAssignableFrom(clazz)) {
  37. throw new IllegalStateException("Error when load extension class(interface: " +
  38. type + ", class line: " + clazz.getName() + "), class "
  39. + clazz.getName() + "is not subtype of interface.");
  40. }
  41. if (clazz.isAnnotationPresent(Adaptive.class)) {
  42. if(cachedAdaptiveClass == null) {
  43. cachedAdaptiveClass = clazz;
  44. } else if (! cachedAdaptiveClass.equals(clazz)) {
  45. throw new IllegalStateException("More than 1 adaptive class found: "
  46. + cachedAdaptiveClass.getClass().getName()
  47. + ", " + clazz.getClass().getName());
  48. }
  49. } else {
  50. try {
  51. clazz.getConstructor(type);
  52. Set<Class<?>> wrappers = cachedWrapperClasses;
  53. if (wrappers == null) {
  54. cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
  55. wrappers = cachedWrapperClasses;
  56. }
  57. wrappers.add(clazz);
  58. } catch (NoSuchMethodException e) {
  59. clazz.getConstructor();
  60. if (name == null || name.length() == 0) {
  61. name = findAnnotationName(clazz);
  62. if (name == null || name.length() == 0) {
  63. if (clazz.getSimpleName().length() > type.getSimpleName().length()
  64. && clazz.getSimpleName().endsWith(type.getSimpleName())) {
  65. name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
  66. } else {
  67. throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
  68. }
  69. }
  70. }
  71. String[] names = NAME_SEPARATOR.split(name);
  72. if (names != null && names.length > 0) {
  73. Activate activate = clazz.getAnnotation(Activate.class);
  74. if (activate != null) {
  75. cachedActivates.put(names[0], activate);
  76. }
  77. for (String n : names) {
  78. if (! cachedNames.containsKey(clazz)) {
  79. cachedNames.put(clazz, n);
  80. }
  81. Class<?> c = extensionClasses.get(n);
  82. if (c == null) {
  83. //保存name和实现类的关系
  84. extensionClasses.put(n, clazz);
  85. } else if (c != clazz) {
  86. throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
  87. }
  88. }
  89. }
  90. }
  91. }
  92. }
  93. } catch (Throwable t) {
  94. IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
  95. exceptions.put(line, e);
  96. }
  97. }
  98. } // end of while read lines
  99. } finally {
  100. reader.close();
  101. }
  102. } catch (Throwable t) {
  103. logger.error("Exception when load extension class(interface: " +
  104. type + ", class file: " + url + ") in " + url, t);
  105. }
  106. } // end of while urls
  107. }
  108. } catch (Throwable t) {
  109. logger.error("Exception when load extension class(interface: " +
  110. type + ", description file: " + fileName + ").", t);
  111. }
  112. }

这样SPI机制的数据都准备好了,接下来我们会用一篇博客来分析Dubbo是如何使用SPI机制的。

发表评论

表情:
评论列表 (有 0 条评论,335人围观)

还没有评论,来说两句吧...

相关阅读

    相关 dubbo解析-SPI机制

     架构体系   框架介绍   概述   Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spri