老板,救救孩子?关注一下吧!

作者 肥又君

前情回顾

在前面介绍了Dubbo SPI 中 ExtensionLoader 的基础使用和原理,本篇重点介绍 Dubbo SPI 对 @Adaptive 注解的处理。

@Adaptive注解简介

@Adaptive 注解是什么东西?

通过名字就能看出,它是用来标注适配器的。Dubbo SPI能根据配置选择实现类了,那还要适配器干啥呢?

简单点说,就是动态选择实现,META-INF 目录下面的配置毕竟还是静态的,要是能根据参数选择实现,那就爽歪歪了。

@Adaptive 注解可以标注在类上,也可以标注在方法上。这里直接先摆出 @Adaptive 注解的功能:

  1. 当 @Adaptive 注解直接标记适配器类上的时候,优先级最高,Dubbo直接使用该类作为接口实现。

  2. 当 @Adaptive 注解标记在方法上时,Dubbo 会解析该方法上的URL类型参数,确定该方法使用的具体实现类。

对于2,有几点说明,一个是这里的URL是org.apache.dubbo.common.URL,先把它理解成一个KV就行,【整个Dubbo系列中,只要没有特殊说明,URL都是指的 org.apache.dubbo.common.URL ,千万别和java.net.URL混了,混了的都是傻】。

另一个要想动态选择实现,就必须要有代理,代理类是跑不掉的,既然我们啥都没干,那肯定是 Dubbo 帮我们做了,后面的代码会详细说。

@Adaptive 注解示例

这个示例重点展示上述第2点的效果,涉及一个接口—— Car,两个实现类—— BMW 和 Audi,还有一个 SPI 配置文件,当然还有一个 Main 方法。

首先,我们先定义 Car 接口:

@SPI("BMW") // @SPI注解表示该接口是个扩展点
public interface Car {  // 看到第一个URL参数了吗?@Adaptive会告诉Dubbo SPI   // 生成的代理类,解析这个URL参数,根据解析到的type参数  // 确定start()方法使用哪个实现类。如果没有type参数,    // 则尝试通过model参数确定实现类    @Adaptive({"type", "model"})   void start(URL url, String who);    @Adaptive({"type", "model"})   void stop(URL url, String who);
}

接下来是两个平淡无奇,但是想坐在里面哭的实现类—— BMW 和 Audi,醒醒,看代码吧:

public class BMW implements Car{    @Override  public void start(URL url, String who) {    System.out.println(who+" is starting the BMW ...");  }   @Override  public void stop(URL url, String who) { System.out.println(who+" is stoping the BMW ...");   }
}   public class Audi implements Car{   @Override  public void start(URL url, String who) {    System.out.println(who+" is starting the Audi ..."); }   @Override  public void stop(URL url, String who) { System.out.println(who+" is stoping the Audi ...");  }
}

接下来是 org.apache.dubbo.common.extension.test.Car 配置文件,注意哦,这个文件是放到 resources/META-INF/dubbo/internal 目录下的,为什么放到这里?翻翻关注公众号翻翻上一篇对ExtensionLoader的分析吧。

BMW=org.apache.dubbo.common.extension.test.BMW
Audi=org.apache.dubbo.common.extension.test.Audi

最后来看Main 方法:

public static void main(String[] args) throws Exception {    // 这里调用的是getAdaptiveExtension()方法,而不是    // 上一篇介绍的getExtension()方法哦!  Car car =  ExtensionLoader .getExtensionLoader(Car.class)  .getAdaptiveExtension();    // 注意这个URL,没有type参数也没有model参数,Dubbo会根据Car接口@SPI注解中   // 指定的默认值选择实现类  URL url = URL.valueOf("test://mycar");   car.start(url, "黄宏斯坦森");  // 输出:黄宏斯坦森 is starting the BMW ...  // 优先根据URL中的type参数选择实现  URL startUrl = URL.valueOf("test://mycar?type=BMW&model=Audi");    car.start(startUrl, "肥又君");   // 输出:肥又君 is starting the BMW ...    // 没有type参数根据URL中的model选择实现 URL stopUrl = URL.valueOf("test://mycar?model=Audi");   car.stop(stopUrl, "百里");  // 百里 is stoping the Audi ...
}

@Adaptive 注解实现原理

前面说了,一个Car对象会根据URL参数切换具体的实现主要是因为Dubbo SPI会生成一个Adaptive的代理类。下面就来分析 ExtensionLoader.getAdaptiveExtension() 方法的如何处理@Adaptive注解以及生成Adaptive代理的。

先来看 ExtensionLoader.getAdaptiveExtension() 方法的实现:

// cachedAdaptiveInstance又是一个Holder,用来缓存Adaptive代理类
private final Holder<Object> cachedAdaptiveInstance = new Holder<>();  public T getAdaptiveExtension() {   // 每个SPI接口最多就只能有一个Adaptive代理对象,这里先查缓存    Object instance = cachedAdaptiveInstance.get();    // double-check处理并发问题   if (instance == null) {   synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get();   if (instance == null) {   // 查找并创建Adaptive代理类实例   instance = createAdaptiveExtension();  cachedAdaptiveInstance.set(instance); // 设置缓存   }   }       }   return (T) instance;
}

下面来看 createAdaptiveExtension() 方法的核心流程:

private volatile Class<?> cachedAdaptiveClass = null;    private void loadClass(Map<String, Class<?>> extensionClasses,  java.net.URL resourceURL,   Class<?> clazz, String name) throws NoSuchMethodException {   ... ...     // 如果发现一个实现类被@Adaptive注解标记了,则记录到cachedAdaptiveClass字段,   // 这个字段后面有用 if (clazz.isAnnotationPresent(Adaptive.class)) {    cacheAdaptiveClass(clazz);  } else  ... ...
}
private Class<?> createAdaptiveExtensionClass() { // 生成Adaptive代理类,返回的code就是生成的Java代码  String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();  // 获取ClassLoader    ClassLoader classLoader = findClassLoader();   // 通过SPI方式获取Compiler实现类 org.apache.dubbo.common.compiler.Compiler compiler =   ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class) .getAdaptiveExtension();    // 编译并加载上面生成的Java代码 return compiler.compile(code, classLoader);
}

简单看看上面示例生成的 Adaptive 代理 —— Car$Adaptive:

public class Car$Adaptive implements org.apache.dubbo.common.extension.test.Car {  public void start(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {    if (arg0 == null) throw new IllegalArgumentException("url == null");  org.apache.dubbo.common.URL url = arg0;    // 尝试先尝试获取type,然后尝试获取model,最后再用"BMW"默认值 String extName = url.getParameter("type", url.getParameter("model", "BMW")); if (extName == null)  throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.test.Car) name from url (" + url.toString() + ") use keys([type, model])");   // 最后还是调用上一篇介绍的getExtension()方法 org.apache.dubbo.common.extension.test.Car extension = (org.apache.dubbo.common.extension.test.Car) ExtensionLoader    .getExtensionLoader(org.apache.dubbo.common.extension.test.Car.class)   .getExtension(extName);     extension.start(arg0, arg1);    }   public void stop(org.apache.dubbo.common.URL arg0, java.lang.String arg1) { ... // 生成的stop()方法与start()方法类似,不再粘贴  }
}

最后,看一下 AdaptiveClassCodeGenerator.generate() 方法是如何生成 Adaptive代理类的:

public String generate() {   StringBuilder code = new StringBuilder();  code.append(generatePackageInfo()); // 生成package部分  code.append(generateImports()); // 生成import部分   code.append(generateClassDeclaration()); // 生成类签名   Method[] methods = type.getMethods(); // 根据SPI接口生成相应的方法代码  for (Method method : methods) { code.append(generateMethod(method));    }   code.append("}"); return code.toString();
}

@Activate注解的事情,下次再说?

Dubbo 2.7.3源码分析——Dubbo SPI(二)相关推荐

  1. 源码分析Dubbo服务注册与发现机制RegistryDirectory)

    RegistryDirectory,基于注册中心的服务发现,本文将重点探讨Dubbo是如何实现服务的自动注册与发现.从上篇文章,得知在消息消费者在创建服务调用器(Invoker)[消费者在初始时]时需 ...

  2. 源码分析Dubbo监控中心实现原理

       Dubbo监控的实现基本原理就是在服务调用时收集服务调用并发度.服务响应时间,然后以一定频率向监控中心汇报统计数据.    1.源码分析MonitorFilter过滤器 过滤器作用    监控过 ...

  3. Dubbo系列(二)源码分析之SPI机制

    Dubbo系列(二)源码分析之SPI机制 在阅读Dubbo源码时,常常看到 ExtensionLoader.getExtensionLoader(*.class).getAdaptiveExtensi ...

  4. 源码分析Dubbo系列文章

       本系列文章主要针对Dubbo2.6.2(dubbox2.8.4)版本,从源码的角度分析Dubbo内部的实现细节,加深对Dubbo的各配置参数底层实现原理的理解,更好的指导Dubbo实践,其目录如 ...

  5. idea 线程内存_Java线程池系列之-Java线程池底层源码分析系列(二)

    课程简介: 课程目标:通过本课程学习,深入理解Java线程池,提升自身技术能力与价值. 适用人群:具有Java多线程基础的人群,希望深入理解线程池底层原理的人群. 课程概述:多线程的异步执行方式,虽然 ...

  6. Spring Boot Dubbo 应用启停源码分析

    作者:张乎兴 来源:Dubbo官方博客 背景介绍 Dubbo Spring Boot 工程致力于简化 Dubbo RPC 框架在Spring Boot应用场景的开发.同时也整合了 Spring Boo ...

  7. 源码分析Dubbo服务消费端启动流程

    通过前面文章详解,我们知道Dubbo服务消费者标签dubbo:reference最终会在Spring容器中创建一个对应的ReferenceBean实例,而ReferenceBean实现了Spring生 ...

  8. 源码分析Dubbo服务提供者启动流程-下篇

    本文继续上文Dubbo服务提供者启动流程,在上篇文章中详细梳理了从dubbo spring文件开始,Dubbo是如何加载配置文件,服务提供者dubbo:service标签服务暴露全流程,本节重点关注R ...

  9. 源码分析Dubbo服务提供者启动流程-上篇

    本节将详细分析Dubbo服务提供者的启动流程,请带着如下几个疑问进行本节的阅读,因为这几个问题将是接下来几篇文章分析的重点内容.  1.什么时候建立与注册中心的连接.  2.服务提供者什么时候向注册中 ...

最新文章

  1. 2021-2027年中国医疗旅游业投资分析及前景预测报告
  2. 批号数量调整单中批次数量和库存数量不一致
  3. 十种创业病 你得了几个?
  4. 堆栈——Windows核心编程学习手札之十八
  5. php页头滚动文字公告,jQuery公告栏文字滚动插件
  6. VS2017 按ctrl+f5执行程序窗口依然一闪而过的问题(图文)
  7. [SQL Server]树形结构的创建
  8. Could not resolve the package 'english_words' in 'package:english_words/english_words.dart'
  9. 搭建开发环境以及STM32固件库移植
  10. 【云和恩墨大讲堂】视频课程震撼来袭-SQL优化精选案例
  11. maccms10自动播放下一集
  12. 安装安全防护软件有助于保护计算机不受侵害,安装安全防护软件有助于保护计算机不受病毒侵害。...
  13. c++课程设计图书馆管理系统总结
  14. python 布莱克舒尔斯_布莱克—舒尔斯期权定价模型
  15. FMI人工智能与大数据线下沙龙第869期北京场圆满落幕
  16. 计算机学院刘岗,刘岗-中国科学院大学-UCAS
  17. OpenGL ES之GLSL实现多种“马赛克滤镜”效果
  18. PPT、word破解加密文档
  19. 西瓜皮——被丢掉的真金白银,夏天的健康守护神
  20. “善弈者通盘无妙手”:网易的To B棋局

热门文章

  1. 3DMax烘焙CompleteMap贴图导入进Unity3D
  2. springboot 集成 swagger 和knife4j
  3. 清华大学 陈鑫 计算机,我院学子在第七届海峡两岸信息服务创新大赛中获佳绩...
  4. 数据库事务和一致性处理
  5. 程序中有游离的...
  6. 学用计算机图片,电脑怎么截图?1分钟教你学会用电脑自带截图工具瞬间截图...
  7. CMFCStatusBar 状态栏字体颜色为灰色
  8. MFC 设置static(标签)控件背景透明
  9. 判断魔方阵c语言程序设计_C语言编程,输出魔方阵
  10. autocad锐龙英特尔_到底差多少?十代标压酷睿对比锐龙4000H