dubbo如何实现可扩展的,援引官网描述

Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。

Dubbo 改进了 JDK 标准的 SPI 的以下问题:

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
  • 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

定义个接口:

  1. public interface HelloService {
  2. String sayHello();
  3. }

定义两个实现类:

  1. public class DogHelloService implements HelloService {
  2. @Override
  3. public String sayHello() {
  4. return "wang";
  5. }
  6. }
  1. public class HumanHelloService implements HelloService {
  2. @Override
  3. public String sayHello() {
  4. return "hello 你好";
  5. }
  6. }

1.JDK标准的SPI是怎么回事?

ServiceLoader.load方法会加载META-INF/services/目录下定义的接口全限定名文件,内容为实现类。

  1. com.exm.service.impl.DogHelloService
  2. com.exm.service.impl.HumanHelloService

核心为ServiceLoader.LazyIterator迭代器,在load方法被调用时,会初始化该迭代器,如下:

  1. public void reload() {
  2. providers.clear();
  3. lookupIterator = new LazyIterator(service, loader);
  4. }

LazyIterator会读取配置实现类,并通过反射进行实例化(前提要求实现类需要具备无参构造)。

其中hasNextService方法会加载META-INF/services接口文件,并加载到Enumeration<URL> configs中,源码如下:

  1. private boolean hasNextService() {
  2. if (nextName != null) {
  3. return true;
  4. }
  5. if (configs == null) {
  6. try {
  7. //获取配置全路径名。如:META-INF/services/com.exm.service.HelloService
  8. String fullName = PREFIX + service.getName();
  9. if (loader == null)
  10. configs = ClassLoader.getSystemResources(fullName);
  11. else
  12. //并加载到Enumeration<URL> configs中
  13. configs = loader.getResources(fullName);
  14. } catch (IOException x) {
  15. fail(service, "Error locating configuration files", x);
  16. }
  17. }
  18. while ((pending == null) || !pending.hasNext()) {
  19. if (!configs.hasMoreElements()) {
  20. return false;
  21. }
  22. pending = parse(service, configs.nextElement());
  23. }
  24. nextName = pending.next();
  25. return true;
  26. }

迭代器通过nextService获取下一个实现类对象,源码如下,其中包含反射拿到Class对象,并实例化。

  1. private S nextService() {
  2. if (!hasNextService())
  3. throw new NoSuchElementException();
  4. String cn = nextName;
  5. nextName = null;
  6. Class<?> c = null;
  7. try {
  8. //反射实例化
  9. c = Class.forName(cn, false, loader);
  10. } catch (ClassNotFoundException x) {
  11. fail(service,
  12. "Provider " + cn + " not found");
  13. }
  14. if (!service.isAssignableFrom(c)) {
  15. fail(service,
  16. "Provider " + cn + " not a subtype");
  17. }
  18. try {
  19. //转强制化为接口,并放入LinkedHashMap<String,S> providers中
  20. S p = service.cast(c.newInstance());
  21. providers.put(cn, p);
  22. return p;
  23. } catch (Throwable x) {
  24. fail(service,
  25. "Provider " + cn + " could not be instantiated",
  26. x);
  27. }
  28. throw new Error(); // This cannot happen
  29. }

为什么说JDK的SPI会一次性加载并实例化所有的扩展呢?

因为在调load时,实际构造了ServiceLoader.LazyIterator,如果想找到某个扩展实现,需要迭代器遍历所有的实现才可以。

️如果其中有一个实例化或cast时异常,后边所有都将无法遍历。

️如果某个类的实例化耗时很长,并没用到,会造成资源浪费

编写一个测试方法:

  1. public static void main(String[] args) {
  2. final ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class);
  3. for (HelloService helloService : helloServices){
  4. System.out.println(helloService.getClass().getName() + ":" + helloService.sayHello());
  5. }
  6. }

测试输出:

  1. com.exm.service.impl.DogHelloService:wang
  2. com.exm.service.impl.HumanHelloService:hello 你好

2.Dubbo是怎么进行改进的呢?

dubbo时如何进行改进的呢?

(1)dubbo定义的SPi文件包含了key,即每个实现类对应一个不同的key,在加载class的时候,会将key和class放入一个map中。

这样在使用者想使用哪个类的实例时,只需要实例化对应的类,无需实例化所有类

(2)Adaptive功能:实现动态的使用扩展点。通过 getAdaptiveExtension方法 统一对指定接口对应的所有扩展点进行封装,通过URL的方式对扩展点来进行动态选择。

2.1 加载所有扩展点,选择性实例化

  1. public class Main {
  2. public static void main(String[] args) {
  3. // 获取扩展加载器
  4. ExtensionLoader<HelloService> extensionLoader = ExtensionLoader.getExtensionLoader(HelloService.class);
  5. // 遍历所有的支持的扩展点,并将key与实现类进行关联
  6. Set<String> extensions = extensionLoader.getSupportedExtensions();
  7. for (String extension : extensions){
  8. String result = extensionLoader.getExtension(extension).sayHello();
  9. System.out.println(result);
  10. }
  11. }
  12. }

ExtensionLoader.getSupportedExtensions会加载所有扩展类(但没有实例化)。然后通过extensionLoader.getExtension对指定key进行实例化,这一点是与JDK不同的。

我们看下具体是怎么加载的,getSupportedExtensions为入口,最终会通过loadDirectory进行加载

  1. Set<String> getSupportedExtensions()
  2. Map<String, Class<?>> getExtensionClasses()
  3. Map<String, Class<?>> loadExtensionClasses()
  4. void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst)

(1)getSupportedExtensions方法会返回所有扩展点的key,供用户使用。

  1. public Set<String> getSupportedExtensions() {
  2. Map<String, Class<?>> clazzes = this.getExtensionClasses();
  3. //获取到所有扩展点后,将key放入TreeSet中(按字符串排序)
  4. return Collections.unmodifiableSet(new TreeSet(clazzes.keySet()));
  5. }

loadDirectory加载文件的来源为以下6个部分,兼容了JDK路径。

同时加载有顺序,越靠前越优先加载

  1. private Map<String, Class<?>> loadExtensionClasses() {
  2. this.cacheDefaultExtensionName();
  3. Map<String, Class<?>> extensionClasses = new HashMap();
  4. this.loadDirectory(extensionClasses, "META-INF/dubbo/internal/", this.type.getName(), true);
  5. this.loadDirectory(extensionClasses, "META-INF/dubbo/internal/", this.type.getName().replace("org.apache", "com.alibaba"), true);
  6. this.loadDirectory(extensionClasses, "META-INF/dubbo/", this.type.getName());
  7. this.loadDirectory(extensionClasses, "META-INF/dubbo/", this.type.getName().replace("org.apache", "com.alibaba"));
  8. this.loadDirectory(extensionClasses, "META-INF/services/", this.type.getName());
  9. this.loadDirectory(extensionClasses, "META-INF/services/", this.type.getName().replace("org.apache", "com.alibaba"));
  10. return extensionClasses;
  11. }

(2)在Set中获取到扩点类对应的key,通过getExtension获取对应class的实例(包含通过setter进行依赖注入)

  1. public T getExtension(String name) {
  2. if (StringUtils.isEmpty(name)) {
  3. throw new IllegalArgumentException("Extension name == null");
  4. } else if ("true".equals(name)) {
  5. return this.getDefaultExtension();
  6. } else {
  7. Holder<Object> holder = this.getOrCreateHolder(name);
  8. Object instance = holder.get();
  9. if (instance == null) {
  10. synchronized(holder) {
  11. instance = holder.get();
  12. if (instance == null) {
  13. //创建对应class的实例,完成依赖注入
  14. instance = this.createExtension(name);
  15. holder.set(instance);
  16. }
  17. }
  18. }
  19.  
  20. return instance;
  21. }
  22. }

createExtension方法是实例化的核心,实现了IOC和AOP,注释如下:

  1. private T createExtension(String name) {
  2. //获取name对应的class
  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. //实例化
  11. EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
  12. instance = (T) EXTENSION_INSTANCES.get(clazz);
  13. }
  14. //依赖注入(IOC)
  15. injectExtension(instance);
  16. //包装器(AOP)
  17. Set<Class<?>> wrapperClasses = cachedWrapperClasses;
  18. if (CollectionUtils.isNotEmpty(wrapperClasses)) {
  19. for (Class<?> wrapperClass : wrapperClasses) {
  20. instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
  21. }
  22. }
  23. initExtension(instance);
  24. return instance;
  25. } catch (Throwable t) {
  26. throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
  27. type + ") couldn't be instantiated: " + t.getMessage(), t);
  28. }
  29. }

2.2 getAdaptiveExtension根据URL参数动态获取相应的扩展点

  1. public class AdaptiveMain {
  2. public static void main(String[] args) {
  3. URL url = URL.valueOf("test://localhost/hello?hello.service=dog");
  4. HelloService adaptiveExtension = ExtensionLoader.getExtensionLoader(HelloService.class).getAdaptiveExtension();
  5. String msg = adaptiveExtension.sayHello(url);
  6. System.out.println(msg);
  7. }
  8. }

(1)核心代码为ExtensionLoader.getAdaptiveExtension方法

  1. public T getAdaptiveExtension() {
  2. Object instance = cachedAdaptiveInstance.get();
  3. if (instance == null) {
  4. if (createAdaptiveInstanceError != null) {
  5. throw new IllegalStateException("Failed to create adaptive instance: " +
  6. createAdaptiveInstanceError.toString(),
  7. createAdaptiveInstanceError);
  8. }
  9.  
  10. synchronized (cachedAdaptiveInstance) {
  11. instance = cachedAdaptiveInstance.get();
  12. if (instance == null) {
  13. try {
  14. //创建自适应扩展
  15. instance = createAdaptiveExtension();
  16. cachedAdaptiveInstance.set(instance);
  17. } catch (Throwable t) {
  18. createAdaptiveInstanceError = t;
  19. throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
  20. }
  21. }
  22. }
  23. }
  24.  
  25. return (T) instance;
  26. }

(2)创建自适应扩展类实例

  1. private T createAdaptiveExtension() {
  2. try {
  3. //获取自适应扩展类,并实例化,然后通过setter注入依赖
  4. return injectExtension((T) getAdaptiveExtensionClass().newInstance());
  5. } catch (Exception e) {
  6. throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
  7. }
  8. }

(3)生成自适应扩展类class

  1. private Class<?> getAdaptiveExtensionClass() {
  2. getExtensionClasses();
  3. if (cachedAdaptiveClass != null) {
  4. return cachedAdaptiveClass;
  5. }
  6. return cachedAdaptiveClass = createAdaptiveExtensionClass();
  7. }

(4)加载类扩展点(与上文相同)

  1. private Map<String, Class<?>> getExtensionClasses() {
  2. Map<String, Class<?>> classes = cachedClasses.get();
  3. if (classes == null) {
  4. synchronized (cachedClasses) {
  5. classes = cachedClasses.get();
  6. if (classes == null) {
  7. classes = loadExtensionClasses();
  8. cachedClasses.set(classes);
  9. }
  10. }
  11. }
  12. return classes;
  13. }

(5)创建自适应扩展class,动态生成代码,并进行编译

  1. private Class<?> createAdaptiveExtensionClass() {
  2. String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
  3. ClassLoader classLoader = findClassLoader();
  4. org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
  5. return compiler.compile(code, classLoader);
  6. }

得到如下class:

  1. package com.exm.service;
  2. import org.apache.dubbo.common.extension.ExtensionLoader;
  3. public class HelloService$Adaptive implements com.exm.service.HelloService {
  4. public java.lang.String sayHello() {
  5. throw new UnsupportedOperationException("The method public abstract java.lang.String com.exm.service.HelloService.sayHello() of interface com.exm.service.HelloService is not adaptive method!");
  6. }
  7. public java.lang.String sayHello(org.apache.dubbo.common.URL arg0) {
  8. if (arg0 == null) throw new IllegalArgumentException("url == null");
  9. org.apache.dubbo.common.URL url = arg0;
  10. String extName = url.getParameter("hello.service", "human");
  11. if(extName == null) throw new IllegalStateException("Failed to get extension (com.exm.service.HelloService) name from url (" + url.toString() + ") use keys([hello.service])");
  12. com.exm.service.HelloService extension = (com.exm.service.HelloService)ExtensionLoader.getExtensionLoader(com.exm.service.HelloService.class).getExtension(extName);
  13. return extension.sayHello(arg0);
  14. }
  15. }

可以看到自适应只支持具有adaptive注解的方法,并且参数汇总需要有URL参数。

具体逻辑,是通过获取URL参数中的变量,ExtensionLoader.getExtensionLoader().getExtension(name)获取具体的class实例,完成调用。

(6)通过setter注入依赖

  1. private T injectExtension(T instance) {
  2.  
  3. if (objectFactory == null) {
  4. return instance;
  5. }
  6.  
  7. try {
  8. for (Method method : instance.getClass().getMethods()) {
  9. if (!isSetter(method)) {
  10. continue;
  11. }
  12. /**
  13. * Check {@link DisableInject} to see if we need auto injection for this property
  14. */
  15. if (method.getAnnotation(DisableInject.class) != null) {
  16. continue;
  17. }
  18. Class<?> pt = method.getParameterTypes()[0];
  19. if (ReflectUtils.isPrimitives(pt)) {
  20. continue;
  21. }
  22.  
  23. try {
  24. String property = getSetterProperty(method);
  25. //获取到Adaptive对象
  26. Object object = objectFactory.getExtension(pt, property);
  27. if (object != null) {
  28. method.invoke(instance, object);
  29. }
  30. } catch (Exception e) {
  31. logger.error("Failed to inject via method " + method.getName()
  32. + " of interface " + type.getName() + ": " + e.getMessage(), e);
  33. }
  34.  
  35. }
  36. } catch (Exception e) {
  37. logger.error(e.getMessage(), e);
  38. }
  39. return instance;
  40. }

(7)上文中的objectFactory是ExtensionFactory实例,其实现类包含SpiExtensionFactory和SpringExtensionFactory,一个是dubbo的扩展工厂,一个是Spring的工厂;前者只支持type是SPI的接口,并生成自适应类;后者从Spring容器中获取。

在依赖注入时,会在两个容器中遍历,如下:

  1. public <T> T getExtension(Class<T> type, String name) {
  2. for (ExtensionFactory factory : factories) {
  3. T extension = factory.getExtension(type, name);
  4. if (extension != null) {
  5. return extension;
  6. }
  7. }
  8. return null;
  9. }

(8)另外,还有一个实现类是AdaptiveExtensionFactory是默认的@Adaptive类,即被该注解修饰的类是自适应类,就不会动态生成了。

在getExtensionClasses()加载ExtensionFactory扩展class时,如果扩点类被Adaptive注解修饰,则将缓存在ExtensionLoader.cachedAdaptiveClass中;

在getAdaptiveExtensionClass方法中,直接返回,不需要生成自适应类

  1. private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
  2. if (!type.isAssignableFrom(clazz)) {
  3. throw new IllegalStateException("Error occurred when loading extension class (interface: " +
  4. type + ", class line: " + clazz.getName() + "), class "
  5. + clazz.getName() + " is not subtype of interface.");
  6. }
  7. //判断类事都是Adaptive类,是的话就缓存,在getAdaptiveExtensionClass时直接返回
  8. if (clazz.isAnnotationPresent(Adaptive.class)) {
  9. cacheAdaptiveClass(clazz);
  10. } else if (isWrapperClass(clazz)) {
  11. cacheWrapperClass(clazz);
  12. } else {
  13. clazz.getConstructor();
  14. if (StringUtils.isEmpty(name)) {
  15. name = findAnnotationName(clazz);
  16. if (name.length() == 0) {
  17. throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
  18. }
  19. }
  20.  
  21. String[] names = NAME_SEPARATOR.split(name);
  22. if (ArrayUtils.isNotEmpty(names)) {
  23. cacheActivateClass(clazz, names[0]);
  24. for (String n : names) {
  25. cacheName(clazz, n);
  26. saveInExtensionClass(extensionClasses, clazz, n);
  27. }
  28. }
  29. }
  30. }

综上,dubbo加载class扩展与实例化是分开的,可以通过指定key实例化某一个class;

dubbo支持IOC和AOP;

同时,dubbo结合SPI与Adaptive注解,可以实现对所有扩展class封装,然后根据URL参数动态获取指定的class。

3.在注入依赖的时候是否有循环依赖的问题?

在dubbo创建扩展class实例时,会通过setter进行依赖注入,如果存在循环依赖,怎么处理?

在dubbo依赖注入时,除了Spring容器外,从SPI容器中获取,获取的是SPI接口的自适应实现,是新创建的类,所以不存在循环依赖的问题。

牛逼的框架,就是让你一眼看不懂它在干什么   ---me

dubbo是如何实现可扩展的?的更多相关文章

  1. Dubbo源码分析系列---扩展点加载

    扩展点配置: 约定: 在扩展类的jar包内,放置扩展点配置文件:META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔.(摘自dubbo文档) 示例: ...

  2. dubbo系列六、SPI扩展Filter隐式传参

    一.实现Filter接口 1.消费者过滤器:ConsumerTraceFilter.java package com.dubbo.demo.Filter; import com.alibaba.dub ...

  3. Dubbo实践(八)扩展点装饰

    Filter Filter是Dubbo里面非常重要的模块,Dubbo里面日志记录.超时等功能都是在这一部分实现. 如上一节在介绍扩展点加载时所述,在生成Protocol的invoker时,实际上使用了 ...

  4. Dubbo实践(五)扩展Spring Schema

    先回顾Dubbo实践(一)中定义的dubbo-provider.xml: <?xml version="1.0" encoding="UTF-8"?> ...

  5. dubbo是如何实现可扩展的?(二)

    牛逼的框架,看似复杂难懂,思路其实很清晰.---me 上篇文章,在整体扩展思路上进行了源码分析,比较粗糙,现在就某些点再详细梳理下. dubbo SPi的扩展,基于一类.三注解. 一类是Extensi ...

  6. dubbo扩展

    dubbo源码版本:2.5.4 经统计,dubbo一共有31个扩展,如下: ------------------------------------------------------ META-IN ...

  7. Dubbo学习笔记6:Dubbo增强SPI与SPI中扩展点自动包装的实现原理

    在Dubbo整体架构分析中介绍了Dubbo中除了Service和Config层为API外,其他各层均为SPI,为SPI意味着下面各层都是组件化可以被替换的,也就是扩展性比较强,这也是Dubbo比较好的 ...

  8. 从ExtensionLoader理解Dubbo扩展机制

    Dubbo的扩展机制是怎么实现的?最简单的回答就是@SPI. Dubbo的插件化思路来源于Java SPI.   JAVA SPI 机制     SPI的全名为Service Provider Int ...

  9. dubbo源码分析之基于SPI的强大扩展

    https://blog.csdn.net/luoyang_java/article/details/86609045 Dubbo采用微内核+插件体系,使得设计优雅,扩展性强.那所谓的微内核+插件体系 ...

随机推荐

  1. Python爬虫报错:"HTTP Error 403: Forbidden"

    错误原因:主要是由于该网站禁止爬虫导致的,可以在请求加上头信息,伪装成浏览器访问User-Agent. 新增user-agent信息: headers = {'User-Agent':'Mozilla ...

  2. css让文字显示特定行数,多余的显示省略号

    /*css*/ .p{ width: 200px; word-break: break-all; text-overflow: ellipsis; display: -webkit-box; /** ...

  3. pip 和 Conda 镜像站配置

    如果你经常使用 Python,那么你对 pip 和 Conda 一定不陌生,它们作为包管理器,可以非常方便的帮助我们下载需要的 Python 包,但是受限于大多 Python 包的服务器在国外,国内下 ...

  4. Django中间件、csrf跨站请求、csrf装饰器、基于django中间件学习编程思想

    django中间件 中间件介绍 什么是中间件? 官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子.它是一个轻量.低级别的插件系统,用于在全局范围内改变Django的输入和输出. ...

  5. 关闭Mac的Microsoft AutoUpdate

    最近使用Office 发现AutoUpdate一直会启动.我也不需要里面的更新.每次还要把它推出. 网上看到有两种方法,一种是暴力删除,另一种是通过权限限制. 暴力可不是我喜欢的方式,所以选择后者. ...

  6. v-if 和 v-show 的区别

    1.v-if && v-else && v-else-if <span v-if = "4 > 9">hello</span ...

  7. android软件简约记账app开发day05-记账页面条目代码优化和bug解决

    android软件简约记账app开发day05-记账页面条目代码优化和bug解决 今天还是因为该bug又极大的耽误了项目进程,该开发文档都要没有时间来写了. 先说bug吧,在昨天已经实现了页面图标的展 ...

  8. Ubuntu Qt5 Firebird 数据库驱动安装

    Ubuntu Qt5 Firebird 数据库驱动安装 apt install libqt5sql5-ibase

  9. 爬虫亚马逊Bestselling类别产品数据TOP100

    1 # -*- coding: utf-8 -*- 2 # @Time : 2020/9/11 16:23 3 # @Author : Chunfang 4 # @Email : 3470959534 ...

  10. .Net中字符串不变性与相等判断的特殊场景

    今天写bug的时候帮同事解决了一个有趣的问题,可能很多人都会答错.分享给大家. 问题 请看以下例子,并回答问题. var s1 = "12"; var s2 = "12& ...