从源码学dubbo

ExtensionLoader解析

2017-11-22  本文已影响62人  醉酒长歌

版本

2.5.7

ExtensionLoader机制

Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。
Dubbo 改进了 JDK 标准的 SPI 的以下问题:

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

ExtensionLoader的作用就是加载所有打上了@SPI注解的接口,并根据配置进行实例化、封装,包括dubbo自己的服务调用、暴露等功能,也是使用这种方式实现的。

加载过程

1、实例化ExtensionLoader

构造方法:

    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

从代码中可以看出,ExrtensionLoaderSPI是一一对应的关系,且每个封装了SPIExrtensionLoader都保存了一个objectFactory对象,而objectFactory指向的都是ExrtensionLoader的adaptive实现,即AdaptiveExtensionFactory

1.1 AdaptiveExtension实例化过程

getAdaptiveExtension的实例化使用了互斥锁,检查了是否有实例化异常 的缓存后,通过createAdaptiveExtension()方法实例化对象。
在实例化的过程中,会再次执行getExtensionClasses()方法检查是否已加载过配置文件。 以ExtensionFactory举例,因为在dubbo-common的配置文件中,已经默认配置了ExtensionFactory的两个实现,且其AdaptiveExtensionFactory上打了@Adaptive注解,所以默认情况下会返回缓存中,第一次加载配置文件时初始化的AdaptiveExtensionFactory类。
但如果缓存中没有,比如Protocol就没有在实现类类上注解@Adaptive,也没关系,ExrtensionLoader会通过createAdaptiveExtensionClass()方法生成代码,再编译成class字节码。
Protocol接口源码

package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.Adaptive;
import com.alibaba.dubbo.common.extension.SPI;

@SPI("dubbo")
public interface Protocol {
    int getDefaultPort();
    void destroy();
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
}

Protocol动态生成的实现类:

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }
    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }
    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) 
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) 
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }

    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg1 == null) 
            throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
}

可以看出,打了@Adaotive注解的方法,都会动态生成根据URL获取信息的代码,再组合成对应的实现类,并存储到ExtensionLoader的静态变量中,以后就可以通过ExtensionLoader加载对应的SPI实现,然后执行业务逻辑。

2、获取ExtensionLoader

名称 配置位置 getExtension方法实现
SpiExtensionFactory dubbo-common ExtensionLoader的静态Map中读取对象,若没有,则通过new关键字创建ExtensionLoader
SpringExtensionFactory dubbo-config/dubbo-config-spring 从Spring上下文中读取对象

ExtensionFactory也是一种SPI,所有的SPI都是通过ExtensionLoader来封装、加载的,每个被封装的对象中都由ExtensionFactory objectFactory属性持有一个ExtensionFactory的Adaptive实现AdaptiveExtensionFactory,而AdaptiveExtensionFactory又由List<ExtensionFactory> factories持有两个对象SpiExtensionFactorySpringExtensionFactory
他们的对比如下:

名称 配置位置 getExtension方法实现
SpiExtensionFactory dubbo-common ExtensionLoader的静态Map中读取对象,若没有,则通过new关键字创建ExtensionLoader
SpringExtensionFactory dubbo-config/dubbo-config-spring 从Spring上下文中读取对象

当执行AdaptiveExtensionFactorygetExtension()方法时,它会轮询二者之中有没有要返回的对象,优先SPI的方式。

总结

通过ExtensionLoader机制,dubbo封装了所有SPI实现,并能够使用@Adaptive注解指定默认的实现,或者注解在方法上,动态将不同实现类的A\B\C方法组合成新的实现类,在灵活、可配、按需加载等方面做的非常出色。

上一篇 下一篇

猜你喜欢

热点阅读