【Dubbo】SPI 机制原理分析 --自适应扩展点(Adaptive)

你的名字 2023-01-07 11:27 337阅读 0赞

前篇: 【Dubbo】SPI 机制原理分析 —静态扩展点

首先来看一个概念,扩展的自适应实例。如果称它为扩展代理类,可能更好理解些,扩展的自适应实例其实就是一个 Extension 的代理,它实现了扩展点接口。在调用扩展点的接口方法时,会根据实际的参数来决定要使用哪个扩展。

比如一个 IRepository 的扩展点,有一个 save 方法。有两个实现 MysqlRepository 和 MongoRepository。IRepository的自适应实例在调用接口方法的时候,会根据 save 方法中的参数,来决定要调用哪个 IRepository的实现。

  • 如果方法参数中有 repository=mysql,那么就调用 MysqlRepository 的 save 方法。
  • 如果 repository=mongo,就调用 MongoRepository 的 save 方法。

和面向对象的延迟绑定很类似。

问题一:为什么Dubbo会引入扩展自适应实例的概念呢?

首先,Dubbo中的配置有两种。一种是固定的系统级别的配置,在 Dubbo 启动之后就不会再改了。还有一种是运行时的配置,可能对于每一次的 RPC,这些配置都不同。

第一种很好理解,这里解释一下第二种。因为 dubbo 是 url驱动,即服务的配置信息都是通过&拼接在 url 之后,换句话说,当 Provider 收到调用请求时,其相关配置是通过查 url 后的参数获得;这样做的目的是, Consumer 在注册中心拿到相应服务的 url 后,可以根据自身的配置对请求 url 再次进行拼接(修改)。因此,对于 Dubbo 而言,每一次的 RPC 调用的参数都是未知的,只有在运行时,根据这些参数才能做出正确的决定。

那么问题就来了,在 Spring bean 实例化时,如果它依赖某个扩展点,在进行依赖注入时,如何知道该使用哪个具体的扩展实现?所以,这时候就需要一个代理模式了,它实现了扩展点接口,方法内部可以根据运行时参数,动态的选择合适的扩展实现。而这个代理就是自适应实例。

自适应扩展实例在 Dubbo 中的使用非常广泛,Dubbo中,每一个扩展都会有一个自适应类,如果我们没有提供,Dubbo会使用字节码工具为我们自动生成一个。所以我们基本感觉不到自适应类的存在。

问题二:如何标识自适应扩展呢?

@Adaptive 就是一个自适应扩展点的标识。它可以修饰在类上,也可以修饰在方法上面。

类级别:

  • 用于具体扩展
  • 代表实现一个装饰类,类似于设计模式中的装饰模式,它主要作用是返回指定类
  • 目前在整个系统中 AdaptiveCompiler、AdaptiveExtensionFactory 这两个类拥有该注解

方法级别:

  • 用于扩展点(接口)中的方法
  • 表示需要生成一个动态代理,方法内部会根据方法的参数,来决定使用哪个扩展

PS:说简单点,如果作用在类上,就相当于自己定义了个现成的代理;如果作用在方法上,运行时会动态生成一个 Adaptive 实例。

1.代码示例

1.1 类级别

示例,传入扩展点(Compiler 接口),返回 AdaptiveCompiler。

  1. // 注意,使用自适应模式时,调用的方法是 getAdaptiveExtension(),不是 getExtension(key)。
  2. Compiler compiler = ExtensionLoader.getExtensionLoader(Compiler.class).getAdaptiveExtension();
  3. System.out.println(compiler.getClass());

在这里插入图片描述

问题一:getAdaptiveExtension() 为什么是返回 AdaptiveCompiler 呢?

我们来看看 META-INF/dubbo/internal/org.apache.dubbo.common.compiler.Compiler

在这里插入图片描述
AdaptiveCompiler 被加载就是因为第一行的原因?错,是因为 getAdaptiveExtension() 表示获取适配器。

问题二:AdaptiveCompiler 有什么用?

打开 AdaptiveCompiler 类,看到这个类上面有一个注解@Adaptive。

在这里插入图片描述

=> AdaptiveCompiler其实就是个适配器,适配的是 JdkCompiler 和 JavassistCompiler(真正提供扩展点实现)
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkzNTkyNw_size_16_color_FFFFFF_t_70_pic_center 1

问题三:那这两种实现,到底该使用哪种?或者说如何适配的?

  1. @Adaptive
  2. public class AdaptiveCompiler implements Compiler {
  3. private static volatile String DEFAULT_COMPILER;
  4. public static void setDefaultCompiler(String compiler) {
  5. DEFAULT_COMPILER = compiler;
  6. }
  7. // 关注compile方法,看他调用jdkCompiler还是javassistCompiler的compile方法
  8. @Override
  9. public Class<?> compile(String code, ClassLoader classLoader) {
  10. Compiler compiler;
  11. ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
  12. // name
  13. String name = DEFAULT_COMPILER;
  14. // 首先根据name进行选择
  15. if (name != null && name.length() > 0) {
  16. compiler = loader.getExtension(name);
  17. // 否则加载默认扩展点实现
  18. } else {
  19. compiler = loader.getDefaultExtension();
  20. }
  21. return compiler.compile(code, classLoader);
  22. }
  23. }

在这里插入图片描述

1.2 方法级别

比如 Protocol 接口,它里面定义了 export 和 refer 两个抽象方法,这两个方法分别带有@Adaptive 的标识,标识是一个自适应方法。
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkzNTkyNw_size_16_color_FFFFFF_t_70_pic_center 2

Protocol 是一个通信协议接口,具体有多种实现,谁来执行这个选择呢?答:运行时生成的动态代理 Protocol$Adaptive

在这里插入图片描述
可以看到,如果 url 中没有携带 Protocol 设置,就采用默认的 dubbo 协议,然后再通过 ExtensionLoader 去获取具体的扩展实例(到这到这里其实就是静态扩展点了)。
在这里插入图片描述
这里的方法层面的 Adaptive 就决定了当前这个方法会采用何种协议来发布服务。

2.源码分析

其中原理从 getAdaptiveExtension() 开始分析

getAdaptiveExtension()

  1. public T getAdaptiveExtension() {
  2. // cachedAdaptiveInstance是一个缓存,在dubbo中大量用到了这种内存缓存
  3. Object instance = cachedAdaptiveInstance.get();
  4. if (instance == null) {
  5. if (createAdaptiveInstanceError == null) {
  6. synchronized (cachedAdaptiveInstance) {
  7. instance = cachedAdaptiveInstance.get();
  8. if (instance == null) {
  9. try {
  10. // 很明显,这里是创建一个自适应扩展点的实现
  11. instance = createAdaptiveExtension();
  12. cachedAdaptiveInstance.set(instance);
  13. } catch (Throwable t) {
  14. createAdaptiveInstanceError = t;
  15. throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
  16. }
  17. }
  18. }
  19. } else {
  20. throw new IllegalStateException("Failed to create adaptive instance: " +
  21. createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
  22. }
  23. }
  24. return (T) instance;
  25. }

createAdaptiveExtension()

  1. private T createAdaptiveExtension() {
  2. try {
  3. // 1.获得一个自适应扩展点实例
  4. // 2.进行依赖注入
  5. return injectExtension((T) getAdaptiveExtensionClass().newInstance());
  6. } catch (Exception e) {
  7. throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
  8. }
  9. }

getAdaptiveExtensionClass()

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

cachedAdaptiveClass 是一个类级别自适应扩展点,表示告诉 dubbo spi loader,“我是一个自适应扩展点,来加载我吧”。

cachedAdaptiveClass 应该是在加载解析 /META-INF/dubbo 下的扩展点的时候加载进来的。在加载完之后如果这个类有@Adaptive 标识,则会赋值赋值而给 cachedAdaptiveClass

如果 cachedAdaptiveClass 不存在,dubbo 会动态生成一个代理类 Protocol$Adaptive.(前面的名字 protocol 是根据当前 ExtensionLoader 所加载的扩展点来定义的)

createAdaptiveExtensionClass()

动态生成字节码,然后进行动态加载。如果加载的是 Protocol.class,应该是 Protocol$Adaptive

  1. private Class<?> createAdaptiveExtensionClass() {
  2. // 生成动态代理的类的字符串
  3. // 这个 cachedDefaultName 实际上就是扩展点接口的@SPI 注解对应的名字
  4. // 如果此时加载的是 Protocol.class,那么 cachedDefaultName=dubbo
  5. String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
  6. ClassLoader classLoader = findClassLoader();
  7. org.apache.dubbo.common.compiler.Compiler compiler =
  8. ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).
  9. getAdaptiveExtension();
  10. return compiler.compile(code, classLoader);
  11. }

injectExtension()

对于扩展点进行依赖注入,简单来说就是如果当前加载的扩展点中存在一个成员属性(对象),并且提供了 set 方法,那么这个方法就会执行依赖注入

  1. private T injectExtension(T instance) {
  2. try {
  3. if (objectFactory != null) {
  4. // objectFactory这里用到了
  5. // 获得实例对应的方法,判断方法是否是一个set方法
  6. for (Method method : instance.getClass().getMethods()) {
  7. if (isSetter(method)) {
  8. // 可以选择禁用依赖注入
  9. if (method.getAnnotation(DisableInject.class) != null) {
  10. continue;
  11. }
  12. // 获得方法的参数,这个参数必须是一个对象类型并且是一个扩展点
  13. Class<?> pt = method.getParameterTypes()[0]; // 获得这个方法的参数类型
  14. if (ReflectUtils.isPrimitives(pt)) {
  15. // 如果不是对象类型,就跳过
  16. continue;
  17. }
  18. try {
  19. // 获得这个方法的属性名称
  20. String property = getSetterProperty(method);
  21. // 根据class以及name,使用自适应扩展点进行加载,并赋值到当前set方法中
  22. Object object = objectFactory.getExtension(pt, property);
  23. if (object != null) {
  24. // 调用set方法进行赋值
  25. method.invoke(instance, object);
  26. }
  27. } catch (Exception e) {
  28. logger.error("Failed to inject via method " + method.getName()
  29. + " of interface " + type.getName() + ": " + e.getMessage(), e);
  30. }
  31. }
  32. }
  33. }
  34. } catch (Exception e) {
  35. logger.error(e.getMessage(), e);
  36. }
  37. return instance;
  38. }

在 injectExtension 这个方法中,入口处的代码首先判断了 objectFactory 这个对象是否为空。这个是在哪里初始化的呢? 实际上在获得 ExtensionLoader 的时候,就对 objectFactory 进行了初始化。

在这里插入图片描述
然后通过 ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()去获得一个自适应的扩展点。进入 ExtensionFactory 这个接口,可以看到它是一个扩展点

在这里插入图片描述

并且有一个自己实现的自适应扩展点 AdaptiveExtensionFactory

在这里插入图片描述

  1. /**
  2. *@Adaptive 加载到类上表示这是一个自定义的适配器类,表示我们再调用 getAdaptiveExtension 方法的时候,
  3. *不需要走上面这么复杂的过程。会直接加载到 AdaptiveExtensionFactory。
  4. *然后在 getAdaptiveExtensionClass()方法处有判断
  5. */
  6. @Adaptive
  7. public class AdaptiveExtensionFactory implements ExtensionFactory {
  8. private final List<ExtensionFactory> factories;
  9. public AdaptiveExtensionFactory() {
  10. ExtensionLoader<ExtensionFactory> loader =
  11. ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
  12. List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
  13. for (String name : loader.getSupportedExtensions()) {
  14. list.add(loader.getExtension(name));
  15. }
  16. factories = Collections.unmodifiableList(list);
  17. }
  18. @Override
  19. // 我们可以看到除了自定义的自适应适配器类以外,还有两个实现类,一个是 SPI,一个是 Spring,
  20. // 轮询这 2 个,从一个中获取到就返回。
  21. public <T> T getExtension(Class<T> type, String name) {
  22. for (ExtensionFactory factory : factories) {
  23. T extension = factory.getExtension(type, name);
  24. if (extension != null) {
  25. return extension;
  26. }
  27. }
  28. return null;
  29. }
  30. }

发表评论

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

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

相关阅读

    相关 Dubbo扩展注解之@Adaptive

    @Adaptive称为自适应扩展点注解。 在实际应用场景中,一个扩展接口往往会有多种实现类,因为Dubbo是基于URL驱动,所以在运行时,通过传入URL中的某些参数来动态控制

    相关 dubbo 适应SPI机制源码分析

    在之前博客中,我们介绍了JAVA SPI 以及 Dubbo  SPI 的基本使用以及源码分析,通过指定扩展点类型,可以创建扩展点的实现类,但是在dubbo中,有些时候并不期望直