dubbo SPI之@SPI、@Adaptive注解, 以及什么时候动态生成$Adaptive代码

本文基于dubbo2.7.7对如下三个问题分析

  1. @SPI注解的作用
  2. @Adaptive注解的作用,放在Type和Method上的区别和注意点
  3. 什么时候动态生成和编译xxx$Adaptive代码

@SPI注解

  1. dubbo中自定义了SPI机制,将接口和具体实现类分开,一个SPI接口可能有多种实现,用户也可以通过SPI实现自己的扩展。在META-INF/dubbo/internalMETA-INF/dubboMETA-INF/services三个目录下选择一个,新建名为接口全类名的文件,并在文件中填写扩展名extName和具体实现类, 扩展名任意, 但不要重复.
  2. 注意: dubbo在扫描SPI的实现类时内部是通过Map保存,无法保证顺序. 但可以按照如下顺序,META-INF/dubbo/internalMETA-INF/dubboMETA-INF/services 优先级从高到低使用相同扩展名, 覆盖默认实现。
    在这里插入图片描述
    dubbo通过ExtensionLoader来加载这些实现类。dubbo的spi接口必须要加上的@SPI注解, 否则在加载时会抛出异常:
    在这里插入图片描述
    @SPI注解上的value值用于指定默认实现的名字, 当加载实现类之前, 会先从接口的@SPI注解上获取value值,如果未指定,调用ExtensionLoader.getExtensionLoader(CustomInterface.class).getDefaultExtension()返回的是null
    在这里插入图片描述
    在这里插入图片描述

@Adaptive注解

ExtensionLoader#getAdaptiveExtension();是dubbo源码中使用比较多的方案,主要用于在spi多个实现类中找一个数据合适的扩展实现。在dubbo中该方法返回的实现类可以理解为是spi接口的一个代理类,是调用其它实现类的入口。
@Adaptive注解则是用来帮助ExtensionLoader#getAdaptiveExtension();判断要获取哪个实现类.

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    String[] value() default {};
}
  1. value指定的值只有在@Adaptive放在interface的method上时才会有作用, 放在具体实现类class上时根本不会读取该value的值
  2. value的值即为key名称,dubbo会通过key名称查找对应的扩展名extName,例如
    String extName = url.getParameter(“transporter”, “netty”),
    但对部分spi接口做了特殊处理,例如Protocol,不是调用getParameter()方法获取extName, 如下:
    String extName = (url.getProtocol() == null ? “dubbo” : url.getProtocol());
  3. value可以指定多个,按照数组顺序, 优先级从高到底, 直到查到为止.
    如果没有找到value中对应的扩展, 则使用接口的SimpleName, 以驼峰处添加点分隔符,字符串全部转成小写,作为参数获取获取extName例如org.apache.dubbo.xxx.YyyInvokerWrapper, 转成 yyy.invoker.wrapper
  4. 调用ExtensionLoader#getExtension(java.lang.String extName)方法获取该扩展名对应的具体实例对象,未获取到则报错

@Adaptive注解定义可以看出既可以放在类上, 又可以放在方法上. 但却有着不同的意义和响应的限制

用在SPI实现类上作用及注意点

  1. 只能放在实现类上,放在spi接口上不会被dubbo处理,无意义;
  2. 如果多个实现类有@Adaptive注解, 会更具SPI目录的优先级覆盖;但同在META-INF/dubbo/internal目录下,同类名的话则报错;如果已经有一个带有@Adaptive注解的实现类,再调用ExtensionLoader#addExtension则报错.
    这样设计的原因其实很简单:只能有一个实现类带有@Adaptive注解, dubbo内部带有@Adaptive注解的实现类又希望可以被用户覆盖.
  3. 放在实现类上时,则不会再动态生成xxx$Adaptive类。实际上dubbo源码中也就是org.apache.dubbo.common.compiler.support.AdaptiveCompilerorg.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory这样使用, 其它都是放在SPI接口方法上.
    在这里插入图片描述

用在SPI接口方法上作用及注意点

  1. 放在SPI接口的方法上@Adaptive注解中的value属性,才会被处理生效
  2. 动态生成xxx$Adaptive类,动态编译class,例如:org.apache.dubbo.rpc.Protocol, 则生成Protocol$Adaptive源码并编译成class
  3. 当实现类和接口都不存在@Adaptive时,调用#getAdaptiveExtension();则会报如下异常:
    在这里插入图片描述

什么时候动态生成$Adaptive代码

ExtensionLoader在从META-INF/dubbo/internalMETA-INF/dubboMETA-INF/services三个目录加载SPI实现类时,如果实现类上存在@Adaptive注解,则会被缓存到成员变量cachedAdaptiveClass,当调用ExtensionLoader#getAdaptiveExtension()会先判断实例是否存在,不存在则通过cachedAdaptiveClass创建对象,如果cachedAdaptiveClass的仍然为空,则更具SPI接口动态创建xxx$Adaptive代码,xxx代表具体的SPI接口。此时要求接口中必须有@Adaptive注解的方法, 否则报错. 参见源码:org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator
在这里插入图片描述
在这里插入图片描述
org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator#generate()
在这里插入图片描述

动态生成的$Adaptive代码内部是什么逻辑

对方法参数进行null值的一些常规校验, 然后根据@Adaptive注解的value属性值获取SPI扩展名和方法返回值类型获取对应的SPI实现类。
在这里插入图片描述
例如org.apache.dubbo.rpc.Protocol接口动态生成的Protocol$Adaptive代码如下:

package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
    public java.util.List getServers()  {
        throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }
    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg1 == null)
            throw new IllegalArgumentException("url == null");

        org.apache.dubbo.common.URL url = arg1;
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");

        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)
                ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
    public void destroy()  {
        throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }
    public int getDefaultPort()  {
        throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }
    public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null)
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");

        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");

        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)
                ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值