Dubbo:SPI扩展机制

一、JDK的SPI思想

        SPI 的全名为 Service Provider Interface,在面向对象的设计里面,模块之间推荐基于接口编程,而不是实现类进行硬编码,这样做也是为了模块设计的可插拔原则。为了在模块装配的时候不在程序里指明是哪个实现,就需要一种服务发现的机制,JDK 的 SPI 就是为某个接口寻找服务实现的。JDK 提供了服务实现查找的工具类:java.util.ServiceLoader,它会去加载 META-INF/service/目录下的配置文件。

二、Dubbo的SPI扩展机制原理

        Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。

        Dubbo 改进了 JDK 标准的 SPI 的以下问题:

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。

  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。

  • 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

        在扩展类的 jar 包内,放置扩展点配置文件 META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。

        以扩展 Dubbo 的协议为例,在协议的实现 jar 包内放置文本文件:META-INF/dubbo/org.apache.dubbo.rpc.Protocol,内容为:

xxx=com.alibaba.xxx.XxxProtocol

        实现类内容:

package com.alibaba.xxx;
 
import org.apache.dubbo.rpc.Protocol;
 
public class XxxProtocol implements Protocol { 
    // ...
}

        那么,Dubbo 怎么在运行期去判断要加载哪个具体的实例?

三、Dubbo的SPI扩展机制实现

1) 相关注解
  • @SPI 注解
    在某个接口上加上 @SPI 注解后,表明该接口为可扩展接口。

  • @Adaptive 注解
    为了保证 Dubbo 在内部调用具体实现的时候不是硬编码来指定引用哪个实现,也就是为了适配一个接口的多种实现,这样做符合模块接口设计的可插拔原则,也增加了整个框架的灵活性,该注解也实现了扩展点自动装配的特性。

  • @Activate 注解
    扩展点自动激活的注解,就是用条件来控制该扩展点实现是否被自动激活加载,在扩展实现类上面使用,实现了扩展点自动激活的特性,它可以设置两个参数,分别是 group 和 value。

2) 动态生成适配器类

        参考官方文档中给出的类的大致结构如下:

package <扩展点接口所在包>;
 
public class <扩展点接口名>$Adpative implements <扩展点接口> {
    public <@Adaptive注解的接口方法>(<方法参数>) {
        if(是否有URL类型方法参数?) 使用该URL参数
        else if(是否有方法类型上有URL属性) 使用该URL属性
        # <else 在加载扩展点生成自适应扩展点类时抛异常,即加载扩展点失败!>
         
        if(获取的URL == null) {
            throw new IllegalArgumentException("url == null");
        }
 
        根据@Adaptive注解上声明的Key的顺序,从URL获致Value,作为实际扩展点名。
        如URL没有Value,则使用缺省扩展点实现。如没有扩展点, throw new IllegalStateException("Fail to get extension");
 
        在扩展点实现调用该方法,并返回结果。
    }
 
    public <@Adaptive注解的接口方法>(<方法参数>) {
        throw new UnsupportedOperationException("is not adaptive method!");
    }
}

        我们以 Protocol 为例来看下生成的扩展点类的结构:

@SPI("dubbo")
public interface Protocol {

    int getDefaultPort();

    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    void destroy();
}
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.apache.dubbo.rpc.Protocol {
    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.apache.dubbo.rpc.Protocol.getDefaultPort() of interface com.apache.dubbo.rpc.Protocol is not adaptive method!");
    }
    
    public com.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.apache.dubbo.common.URL arg1) throws com.apache.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.apache.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null) throw new IllegalStateException("Fail to get extension(com.apache.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.apache.dubbo.rpc.Protocol extension = (com.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
    
    public com.apache.dubbo.rpc.Exporter export (com.apache.dubbo.rpc.Invoker arg0) throws com.apache.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.apache.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.apache.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.apache.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
	
	public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.apache.dubbo.rpc.Protocol.destroy() of interface com.apache.dubbo.rpc.Protocol is not adaptive method!");
    }
}

        关于上述生成的代码,我们主要注意如下几个问题:

  • 所有未使用 @Adaptive 注解标注的接口方法,默认都会抛出异常;

  • 使用 @Adaptive 注解标注的方法中,其参数中必须有一个参数类型为 URL,或者其某个参数提供了某个方法,该方法可以返回一个 URL 对象;

  • 在方法的实现中会通过 URL 对象获取某个参数对应的参数值,如果在接口的 @SPI 注解中指定了默认值,那么在使用 URL 对象获取参数值时,如果没有取到,就会使用该默认值;

  • 最后根据获取到的参数值,在 ExtensionLoader 中获取该参数值对应的服务提供类对象,然后将真正的调用委托给该服务提供类对象进行处理;

  • 在通过 URL 对象获取参数时,参数key 获取的对应规则是,首先会从 @Adaptive 注解的参数值中获取,如果该注解没有指定参数名,那么就会默认将目标接口的类名转换为 点分形式 作为参数名。


        根据上面的分析,Dubbo Adaptive 的实现机制步骤已经比较清晰了,主要分为如下三个步骤:

  • 加载标注有 @Adaptive 注解的接口,如果不存在,则不支持 Adaptive 机制;

  • 为目标接口按照一定的模板生成子类代码,并且编译生成的代码,然后通过反射生成该类的对象;

  • 结合生成的对象实例,通过传入的 URL 对象,获取指定key 的配置,然后加载该key 对应的类对象,最终将调用委托给该类对象进行处理;

        可以看到,通过这种方式,Dubbo 就实现了一种通过配置参数动态选择所使用的服务 的目的,而实现这种机制的入口主要在 ExtensionLoader.getAdaptiveExtension()方法,查看如下的具体实现:

org.apache.dubbo.common.extension.ExtensionLoader代码片段
private ExtensionLoader(Class<?> type) {
    this.type = type;
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null) {
        throw new IllegalArgumentException("Extension type == null");
    }
    //type必须是接口
    if (!type.isInterface()) { 
        throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
    }
    //必须注解@SPI
    if (!withExtensionAnnotation(type)) { 
        throw new IllegalArgumentException("Extension type (" + type +
                ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
    }
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        //缓存中没有,则新建ExtensionLoader对象放入缓存
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

public T getAdaptiveExtension() {
	//从缓存中获取,每个SPI接口至多只有一个 Adaptive 适配对象
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
        if (createAdaptiveInstanceError != null) {
            throw new IllegalStateException("Failed to create adaptive instance: " +
                    createAdaptiveInstanceError.toString(),
                    createAdaptiveInstanceError);
        }
        synchronized (cachedAdaptiveInstance) {
            instance = cachedAdaptiveInstance.get();
            if (instance == null) {
                try {
                	//查找并创建Adaptive代理类实例
                    instance = createAdaptiveExtension();  
                    cachedAdaptiveInstance.set(instance);
                } catch (Throwable t) {
                    createAdaptiveInstanceError = t; //记录创建自适应扩展点错误信息
                    throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                }
            }
        }
    }
    return (T) instance;
}

private T createAdaptiveExtension() {
    try {
        //获取自适应扩展点实例,并注入依赖实例
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
    }
}

private Class<?> getAdaptiveExtensionClass() {
	//获取扩展点Class
    getExtensionClasses(); 
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    //创建适配扩展点Class并返回
    return cachedAdaptiveClass = createAdaptiveExtensionClass(); 
}

rivate Class<?> createAdaptiveExtensionClass() {
    //拼接自适应扩展点 class 的字符串
    String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
    ClassLoader classLoader = findClassLoader();
    //获取编译器
    org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader); //编译字符串为 class
}

        这里 createAdaptiveExtensionClassCode 方法生成自适应扩展点类的流程,我们就不具体的进行分析,就是一些字符串的拼接操作。

3) 加载具体扩展实例

        通过上面的示例可以看到,Dubbo 在动态生成的适配类中想获取具体的扩展实例,都会调用 getExtension 方法,那么我们来看看在调用 getExtension 方法获取具体实例过程中会做了哪些操作。

org.apache.dubbo.common.extension.ExtensionLoader代码片段
public T getExtension(String name) {
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    //获取或创建扩展点的辅助实例
    final Holder<Object> holder = getOrCreateHolder(name);
    Object instance = holder.get();
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
            	//根据扩展名,获取对应的 class,生成扩展实例
                instance = createExtension(name);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

private T createExtension(String name) {
	//获取扩展名对应的类型Class
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
        	//反射,生成扩展实例
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        //为扩展实例,注入setXXX方法对应的属性值
        injectExtension(instance); 
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        //Wrapper不为空,将当前实例包装返回
        if (CollectionUtils.isNotEmpty(wrapperClasses)) { 
            for (Class<?> wrapperClass : wrapperClasses) {
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                type + ") couldn't be instantiated: " + t.getMessage(), t);
    }
}

private T injectExtension(T instance) {
    if (objectFactory == null) {
        return instance;
    }
    try {
    	//遍历扩展实例的所有方法
        for (Method method : instance.getClass().getMethods()) {
        	//跳过非 setXXX 的方法
            if (!isSetter(method)) {
                continue;
            }
            //跳过 @DisableInject 注解的方法
            if (method.getAnnotation(DisableInject.class) != null) {
                continue;
            }
            Class<?> pt = method.getParameterTypes()[0];
            if (ReflectUtils.isPrimitives(pt)) {
                continue;
            }
            try {
            	//截取方法的属性名
                String property = getSetterProperty(method);
                //从对象工厂获取对应属性名的实例
                Object object = objectFactory.getExtension(pt, property);
                if (object != null) {
                	//反射,调用setXXX方法设置属性值
                    method.invoke(instance, object); 
                }
            } catch (Exception e) {
                logger.error("Failed to inject via method " + method.getName()
                        + " of interface " + type.getName() + ": " + e.getMessage(), e);
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

        这里运用到了两个扩展点的特性,分别是自动装配(IoC)和自动包装(AOP)。

        对于自动装配,这类似与Spring 的属性依赖自动注入,Dubbo 提供了两中扩展工厂实例,对于@SPI注解的扩展实例依赖,从扩展加载器类的缓存中获取自适配扩展实例注入。

public class SpiExtensionFactory implements ExtensionFactory {
    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }
}

public class SpringExtensionFactory implements ExtensionFactory {
    //省略部分代码...
    
    @Override
    @SuppressWarnings("unchecked")
    public <T> T getExtension(Class<T> type, String name) {
        //SPI should be get from SpiExtensionFactory
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            return null;
        }
        for (ApplicationContext context : CONTEXTS) {
            if (context.containsBean(name)) {
                Object bean = context.getBean(name);
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }
        logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());
        if (Object.class == type) {
            return null;
        }
        for (ApplicationContext context : CONTEXTS) {
            try {
                return context.getBean(type);
            } catch (NoUniqueBeanDefinitionException multiBeanExe) {
                logger.warn("Find more than 1 spring extensions (beans) of type " + type.getName() + ", will stop auto injection. Please make sure you have specified the concrete parameter type and there's only one extension of that type.");
            } catch (NoSuchBeanDefinitionException noBeanExe) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Error when get spring extension(bean) for type:" + type.getName(), noBeanExe);
                }
            }
        }
        logger.warn("No spring extension (bean) named:" + name + ", type:" + type.getName() + " found, stop get bean.");
        return null;
    }
}

        对于Spring 的自动装配,Dubbo 会从Spring 的IoC容器(ApplicationContext)中获取注入,该容器实例会在服务暴露、引用的时候被设置到SpringExtensionFactory。

public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {
	//省略代码...
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        SpringExtensionFactory.addApplicationContext(applicationContext);
    }
}

public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
        ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware,
        ApplicationEventPublisherAware {
	//省略代码...
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        SpringExtensionFactory.addApplicationContext(applicationContext);
        supportedApplicationListener = addApplicationListener(applicationContext, this);
    }    

        关于Wrapper对象,这里需要说明的是,其主要作用是为目标对象实现AOP。Wrapper对象有两个特点:

  • 与目标对象实现了同一个接口;
  • 有一个以目标接口为参数类型的构造函数;

这也就是上述createExtension()方法最后封装Wrapper对象时传入的构造函数实例始终可以为instance实例的原因。

四、总结

        本文首先对 JDK的SPI 和 Dubbo的SPI 进行了简单的对比,说明了Dubbo相对于 JDK的SPI 所提供的额外的动态配置性能,然后通过一个示例来展示了 Dubbo的SPI 的使用方式,最后着重讲解了用于实现 Dubbo SPI的ExtensionLoader的实现原理。


引用

官网扩展点加载
官网扩展点适配器类结构

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值