Dubbo/Dubbox的服务暴露(二)-扩展点机制

接上《 Dubbo/Dubbox的服务暴露(一)》

上文书留的疑问,这两句到底在干啥

                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));

                        Exporter<?> exporter = protocol.export(invoker);
                        exporters.add(exporter);

首先我们去看proxyFactory.getInvoker 方法,proxyFactory是一个全局变量,点开看一下。保持了dubbo代码一贯清爽的风格,又没有文档注释。乏开心……

private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

dubbo源码中到处都是 ExtensionLoader.getExtensionLoader(XXXX.class),为避免影响阅读,先要了解下这个类的用途,点进ExtensionLoader这个类去看看大佬们做了些啥,点进去一看,大佬们留注释了。喜极而泣。

package com.alibaba.dubbo.common.extension
/**
 * Dubbo使用的扩展点获取。<p>
 * <ul>
 * <li>自动注入关联扩展点。</li>
 * <li>自动Wrap上扩展点的Wrap类。</li>
 * <li>缺省获得的的扩展点是一个Adaptive Instance。
 * </ul>
 * 
 * @see <a href="http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Service%20Provider">JDK5.0的自动发现机制实现</a>
 * 
 * @author william.liangf
 * @author ding.lid
 *
 * @see com.alibaba.dubbo.common.extension.SPI
 * @see com.alibaba.dubbo.common.extension.Adaptive
 * @see com.alibaba.dubbo.common.extension.Activate
 */
public class ExtensionLoader<T> {
//...............//
}

通过dubbo中少有的文档注释,可以提炼几个关键字SPI,Adaptive,Activate
其中SPI,我之前转过一篇文章《Java spi机制浅谈》
文章有说

java spi的具体约定如下 :
当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。
jdk提供服务实现查找的一个工具类:java.util.ServiceLoader

等等ServiceLoader和ExtensionLoader命名颇像啊,ExtensionLoader.getExtensionLoader(XXXX.class)与ServiceLoader.load(Search.class); 使用方式也颇像啊,点进去看看getExtensionLoader方法都做了些啥,以上文protocol 为例

 private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

该protocol属性的生成过程如下

@SuppressWarnings("unchecked")
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        if(!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        // 检查该接口上是否使用@SPI注解
        if(!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type + 
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }

        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {//EXTENSION_LOADERS是线程安全的map对象(ConcurrentMap),典型的单例,避免针对同一接口生成多个ExtensionLoader实例
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

ExtensionLoader.getExtensionLoader(Protocol.class) 会返回一个ExtensionLoader 的一个实例,后续还通过这个ExtensionLoader 调用了getAdaptiveExtension() 方法?那好继续点击去看。这年后英语在编程领域当道,真是妨碍汉语发展,坐等广电要求编程领域使用汉语编程[滑稽],adaptive是个啥意思呢?ps:这里强烈吐槽百度的翻译结果和例句。

adaptive指有适应性的;倾向于适应能力的,为适应能力而设计的,合适于适应能力的或具有适应能力的。

 @SuppressWarnings("unchecked") 
    public T getAdaptiveExtension() {
        //从缓存中取已经创建好的实例
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) { 
            if(createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            //*注意,主要逻辑全都是在这一行。
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            }
            else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }

很好createAdaptiveExtension(),又是一个unchecked的方法。

 @SuppressWarnings("unchecked")
    private T createAdaptiveExtension() {
        try {
            //先调用 getAdaptiveExtensionClass().newInstance() 方法,那么我们又要了解getAdaptiveExtensionClass() 方法
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
        }
    }

getAdaptiveExtensionClass() 实现如下。

private Class<?> getAdaptiveExtensionClass() {
        //该方法除了一堆缓存操作,内部会执行loadExtensionClasses();方法
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

以上代码我们知道,getAdaptiveExtensionClass() 内部会调用loadExtensionClasses()方法

// 此方法已经getExtensionClasses方法同步过。
    private Map<String, Class<?>> loadExtensionClasses() {
        // 拿到@SPI 注解
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if(defaultAnnotation != null) { //如果找到该注解
            String value = defaultAnnotation.value(); //获得扩展点名
            if(value != null && (value = value.trim()).length() > 0) { //如果指定了扩展点名
                //NAME_SEPARATOR 是一个这样的正则表达式 Pattern.compile("\\s*[,]+\\s*") 作用可想而知
                String[] names = NAME_SEPARATOR.split(value);

                if(names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                if(names.length == 1) cachedDefaultName = names[0];
            }
        }

        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
         //依次加载三个路径下的配置文件
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

上文中loadFile(extensionClasses, XXXXX);方法中使用的几个常量如下。

 private static final String SERVICES_DIRECTORY = "META-INF/services/";

    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";

    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

以 loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY); 为例,看一下META-INF/dubbo/internal下的文件

这里写图片描述

以 META-INF\dubbo\internal\com.alibaba.dubbo.rpc.Protocol 文件为例,其文件内容:

registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol
rest=com.alibaba.dubbo.rpc.protocol.rest.RestProtocol

可见,文件内容按key=value的格式编写,key为扩展点名称,value为扩展点逻辑类名。loadFile的处理逻辑如下,遍历指定目录的文件,按行读取文件内容,判断=号右边的类是否为当前扩展点的实现,如果是将=号左边的作为key,右边的类对象作为value,存储在参数的map中
ps:还有一种配置方式,但已不使用,祥见com.alibaba.dubbo.common.extension.SPI 类的文档注释,loadFile方法依旧兼容老的配置方式
dubbo的扩展点还有一个误区,即其支持包装类的配置,包装类需实现扩展点接口,并含有一个以当前扩展点接口为唯一参数的构造方法。一个典型的包装类配置如下:

filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
/**
 * ListenerProtocol
 * 
 * @author william.liangf
 */
public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;
    // 注意
    public ProtocolFilterWrapper(Protocol protocol){
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }
    /*******/
}

然后我们继续回到getAdaptiveExtensionClass()方法,其在getExtensionClasses();方法后,继续调用createAdaptiveExtensionClass();方法,这个方法比较复杂,只做大概总结。
1.在扩展点所在包名下创建一个类
2.类名规则"public class " + type.getSimpleName() + "$Adpative" + " implements " + type.getCanonicalName()(反射相关知识)
3.对扩展点接口的每个方法做一个封装,这里分为两个处理分支
3.1 方法上没有@Adaptive 注解,以com.alibaba.dubbo.rpc.Protocol 的void destroy(); 方法为例,默认实现为以下,即会抛出UnsupportedOperationException异常。

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!");
    }

3.2 方法上有 @Adaptive 注解,这个需要注意啦
3.2.1 会判断方法上是不是有类型为com.alibaba.dubbo.common.URL 类型的参数,如果没有对应类型的参数,则依次判断参数的类中是否有类型为com.alibaba.dubbo.common.URL的属性,如果都没有则方法实现抛出IllegalStateException异常,如果有与URL有关的参数,则已当前扩展点名为参数,调用 String extName = url.getParameter(扩展点名,默认值); 然后调用ExtensionLoader.getExtensionLoader(扩展点类.class).getExtension(extName); 一个典型的样例如下(为保持一致,这里依旧使用protocol的扩展点,protocol有特殊处理,但不影响阅读理解):

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);
    }

最后调用的getExtension(extName) 来找到扩展点的具体实现,其实看到这感觉已经不需要继续分析getExtension方法了,因为我们上边看到dubbo通过loadFile方法加载了当前扩展点的实现类的对应关系,
在上文所例的文件中有这样一行,

dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol

而我们通过上边样例中的String extName = ((url.getProtocol() == null) ? "dubbo": url.getProtocol());取到的extName 恰恰为dubbo,因为看到这里其实整个扩展点的加载机制已经很清晰了,getExtension(String name) 无非是返回指定名字的扩展类实例,这里抄一个道友的对该方法的简单描述:

getExtension(String name)方法:返回指定名字的扩展类实例,
1)如果指定名字的扩展类不存在,则抛异常。
2)如果指定的名字为“true”,则返回默认的实现类实例,即name= cachedDefaultName;
3)从ExtensionLoader.cachedInstances:ConcurrentHashMap变量中获取该name的实例;
4)若Map中没有该name的实例,则调用createExtension方法创建该实例,并保存到缓存中。

注意:如果当前扩展点含有上文所说的包装类,则会初始化扩展点类的基础上,返回包装类的对象。一个典型的情况是,Protocol的适应性扩展点为dubbo,该方法会返回包装类com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper的实例
关于扩展点的小结:
所谓适应性扩展点的实现,其实是与URL息息相关的。
1.要求方法上必须有Adaptive 注解
2.方法上必须有与com.alibaba.dubbo.common.URL 有关的参数(包含属性,或直接参数)
3.URL的参数中必须有对应的扩展点名称的值或者扩展点有指定默认值,否则会抛出IllegalStateException 异常。
4.可以通过在接口上的SPI注解上定义扩展点的默认值(extName),例:@SPI(FailoverCluster.NAME)
其他参考Adaptive 的文档注释(赞):

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {

    /**
     * 从{@link URL}的Key名,对应的Value作为要Adapt成的Extension名。
     * <p>
     * 如果{@link URL}这些Key都没有Value,使用 用 缺省的扩展(在接口的{@link SPI}中设定的值)。<br>
     * 比如,<code>String[] {"key1", "key2"}</code>,表示
     * <ol>
     * <li>先在URL上找key1的Value作为要Adapt成的Extension名;
     * <li>key1没有Value,则使用key2的Value作为要Adapt成的Extension名。
     * <li>key2没有Value,使用缺省的扩展。
     * <li>如果没有设定缺省扩展,则方法调用会抛出{@link IllegalStateException}。
     * </ol>
     * <p>
     * 如果不设置则缺省使用Extension接口类名的点分隔小写字串。<br>
     * 即对于Extension接口{@code com.alibaba.dubbo.xxx.YyyInvokerWrapper}的缺省值为<code>String[] {"yyy.invoker.wrapper"}</code>
     * 
     * @see SPI#value()
     */
    String[] value() default {};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值