dubbo之SPI

前言

dubbo应该是现阶段最流行的rpc框架之一。其中spi的机制贯穿着dubbo的整个架构。

SPI

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。dubbo2.7.8Demo如下

public class ExtensionLoaderTest implements  Transporter{
    @Override
    public RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException {
        System.out.println("test yoyo bind");
        return null;
    }

    @Override
    public Client connect(URL url, ChannelHandler handler) throws RemotingException {
        System.out.println("test yoyo connect");
        return null;
    }

    public static void main(String[] args) throws RemotingException {
        Transporter transporter = ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
        URL url = URL.valueOf("dubbo://127.0.0.1:28092/?client=yoyo");
        transporter.connect(url,null);
    }
}
  1. 定义SPI接口,ps:这边直接借个了Transporter接口
  2. 创建对应Transporter接口的ExtensionLoader
  3. 通过ExtensionLoader生成Adaptive来实现动态化
ExtensionLoader

ExtensionLoader做为SPI的核心了,你结构如下

public class ExtensionLoader<T> {

    // 全局容器缓存,表示spi接口class-ExtensionLoader的对应关系
    // 通过getExtensionLoader(Class<T> type)方法创建,并缓存
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
    
    // 全局实例缓存,spi接口实现class-其实例的对应关系
    // 通过createExtension(String name, boolean wrap)方法创建,并缓存
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);

    // 当前spi接口class
    private final Class<?> type;

    // SPI容器接口,通过其默认实现AdaptiveExtensionFactory可集成各种容器
    // 1. SpiExtensionFactory,通过ExtensionLoader.getExtensionLoader(type).getAdaptiveExtension()查找dubbo spi中实例
    // 2. SpringExtensionFactory,通过BeanFactoryUtils.getOptionalBean查询spring中实例
    // 通过 injectExtension 在创建 spi实例时,通过反射查询对应的setter方法将对应注入,实现Dubbo IOC
    private final ExtensionFactory objectFactory;

    // dubbo spi class的加载策略,分别对应
    // 1.DubboInternalLoadingStrategy - META-INF/dubbo/internal/
    // 2.DubboLoadingStrategy - META-INF/dubbo/
    // 3.ServicesLoadingStrategy  META-INF/services/
    // 通过 loadExtensionClasses方法能三个目录进行扫描
    private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();

    // 通过loadClass 加载dubbo spi class,如果发现有class实现Adaptive注解的记录它
    // 当调用getAdaptiveExtensionClass获取AdaptiveClass时,如果发现cachedAdaptiveClass有值,则直接做为AdaptiveClass不再根据 spi Adaptive注解生成AdaptiveClass代码
    private volatile Class<?> cachedAdaptiveClass = null;
    
    // 通过loadClass 加载dubbo spi class,如果发现有class构造函数是type记录它
    // 当调用createExtension创建spi实例时,如果此时wrap为true,则会用cachedWrapperClasses中符合Wrapper注解的类包装它,实现dubbo的 AOP
    private Set<Class<?>> cachedWrapperClasses;

    // 通过loadClass 加载dubbo spi class,存储其name-class的对应关系
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

    // 对正常的spi 实现类,记录class - name的关系
    // 方便通过getExtensionName根据class查找名字
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
    // 对正常的spi 实现类,记录name - 类上面Activate注解的信息
    // 方便通过getActivateExtension方法查找
    private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();

    // 存储name- class实例,与cachedClasses对应
    // 对过getExtension实行单例模式
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();

    // 存储type对就的AdaptiveClass实例,与cachedAdaptiveClass对应
    private final Holder<Object> cachedAdaptiveInstance = new Holder<>();

    // 对就type接口上的SPI注解value,用于生成AdaptiveClass用
    private String cachedDefaultName;
}
SPI调用流程

看一下源码。首先我们从这句话开始讲起

// ExtensionLoader.getExtensionLoader(Transporter.class)
// 1. 先检查有没有带SPI的注解,没有带,直接报错
// 2. new ExtensionLoader<T>(type)构造 ExtensionLoader
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type);

// 获取Adaptive实例主要三步骤
// 1. getAdaptiveExtensionClass 获取 Adaptive对象
// 2. newInstance 构建实例
// 3. injectExtension 实现IOC注入对象
public T getAdaptiveExtension();

private Class<?> getAdaptiveExtensionClass() {
        // 触发SPI扫描流程,利用dubbo spi class的加载策略
        getExtensionClasses();
        
        // 如果扫摸到带有Adaptive注解的则直接使用它
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        
        // 利用接口中Spi注解和方法上Adaptive注解生成Class
        return cachedAdaptiveClass createAdaptiveExtensionClass();
 }


// Dubbo IOC 是通过 setter 方法注入依赖。
private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            // 遍历目标类的所有方法
            for (Method method : instance.getClass().getMethods()) {
                // 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
                if (method.getName().startsWith("set")
                    && method.getParameterTypes().length == 1
                    && Modifier.isPublic(method.getModifiers())) {
                    // 获取 setter 方法参数类型
                    Class<?> pt = method.getParameterTypes()[0];
                    try {
                        // 获取属性名,比如 setName 方法对应属性名 name
                        String property = method.getName().length() > 3 ? 
                            method.getName().substring(3, 4).toLowerCase() + 
                            	method.getName().substring(4) : "";
                        // 从 ObjectFactory 中获取依赖对象
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            // 通过反射调用 setter 方法设置依赖
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("fail to inject via method...");
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

例子中生成Transporter$Adaptive

public class Transporter$Adaptive implements org.apache.dubbo.remoting.Transporter {
    public org.apache.dubbo.remoting.Client connect(org.apache.dubbo.common.URL arg0, org.apache.dubbo.remoting.ChannelHandler arg1) throws org.apache.dubbo.remoting.RemotingException {
        if (arg0 == null)
            throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg0;
        // client 值是 Adaptive 注解起的作用
        // netty 默认值是Spi注解起的作用
        String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.remoting.Transporter) name from url (" + url.toString() + ") use keys([client, transporter])");
        
        // 通过在url中取到的extName,然后去真实的实例    
        org.apache.dubbo.remoting.Transporter extension = (org.apache.dubbo.remoting.Transporter) ExtensionLoader.getExtensionLoader(org.apache.dubbo.remoting.Transporter.class).getExtension(extName);
        return extension.connect(arg0, arg1);
    }

    // ...
}

总结Adaptive,spi注解的作用

  1. 在spi实现类中打上Adaptive,则直接使用它。
  2. 在spi接口方法打上Adaptive,作用在url参数,根据参数动态选择实例
  3. 在spi接口类上打上SPI,作为url上值为空时的默认值

主要参考

Dubbo SPI
Dubbo SPI之Adaptive详解
SPI 自适应拓展

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值