欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

dubbo之SPI

程序员文章站 2022-06-01 19:46:27
...

前言

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详解

相关标签: # dubbo dubbo spi