Dubbo的SPI机制

Posted 源码王

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dubbo的SPI机制相关的知识,希望对你有一定的参考价值。

插件机制是Dubbo用于可插拔地扩展底层的一些实现而定制的一套机制,比如dubbo底层的RPC协议、注册中心的注册方式等等。具体的实现方式是参照了JDK的SPI思想,由于JDK的SPI的机制比较简单,满足不了一些复杂的需求,所以dubbo重写了一套SPI机制,实现了类似spring的IOC和AOP的机制,灵活度和扩展性大大得以提升,这套机制很有用,我们如果能深刻理解,完成可以把它从dubbo的代码中剥离出来,用在自己系统中。这篇文章先简单讲一下这套机制:

在dubbo的jar内部的/META-INF/dubbo/internal下会有许多以接口名命名的文件,如图:

这些文件正是用于扩展用途的,我们称之为SPI描述文件。每个文件就代表一种扩展,文件打开后,在文件中会以行为单位配置该扩展接口的具体实现类,每个扩展可以有多个实现,以key-value的形式进行配置。例如打开/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol文件,其中的每一行都是com.alibaba.dubbo.rpc.Protoco接口的一个实现,内容如下图:

在使用时,Dubbo提供了两个类,一个是com.alibaba.dubbo.common.extension包下的ExtensionLoader类,另一个是com.alibaba.dubbo.common.extension包下的ExtensionFactory,使用的代码如下:

  例如我们想通过插件机制获取某个接口扩展类,以com.alibaba.dubbo.rpc.Protocol为例:

 

@Test
    public void getExtensionLoader(){
        ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class) ;
        
        Protocol protocol = (Protocol) extensionLoader.getExtension("dubbo") ;
        
    }

 

 

 

我们对上面的单元测试做个简单分析,我们想获取某个SPI接口实例其实就两步:

1. 获取相应的SPI接口的ExtensionLoader实例

2. 通过ExtensionLoader实例获取相应的SPI接口实例

 

 

而要明白原理,则需要搞清以下4个问题就可以

1.  每个Dubbo的SPI接口上都要有SPI的类注解

2.  每个Dubbo的SPI接口的具体实例,可通过调用其所属的ExtensionLoader实例的getExtension方法来获取:

3. 每个Dubbo的SPI接口的ExtensionLoader实例又是如何产生的呢

4. 每个ExtensionLoader实例中的实例属性objectFactory(类型为ExtensionFactory)的作用

 

以下摘抄出相应的代码来问答上述问题,代码是最好的答案:

代码的执行调用顺序:

  getExtension方法——>createExtension方法——>getExtensionClasses方法——>loadExtensionClasses方法——>loadFile方法

//这段代码用来回答上述第2个问题
    public T getExtension(String name) {
        if (name == null || name.length() == 0)
            throw new IllegalArgumentException("Extension name == null");
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<Object>());
            holder = cachedInstances.get(name);
        }
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name);  //这句才是关键
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }
private T createExtension(String name) {
        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, (T) clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance);  //此处将要完成注入工作,这跟Spring的Bean创建的过程类似,要完成一些依赖注入工作,在后面也能看到ExtensionFactory的作用
       

        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && wrapperClasses.size() > 0) {
          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 + ") could not be instantiated: " + t.getMessage(), t);
     }

}
 // getExtensionClasses方法的实质性代码。
    private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if(defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if(value != null && (value = value.trim()).length() > 0) {
                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;
    }

 

 

//这段代码回答上述第3个问题

//ExtensionLoader的构造方法  
private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

 

//此代码说明 ExtensionFactory 的作用,即上述第4个问题

private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

 

 总结:本文只是对Dubbo的SPI机制做一个粗的说明,其实一句话就能说清楚,动态获取SPI接口的实例 ,最简单的方式就是调用 ExtensionLoader实例 的 T getExtension(String name)方法就可以,传入的参数就是上述SPI描述文件中的key ,而 ExtensionFactory 的作用就是为了完成SPI实例间的依赖注入。后面我们还会针对Dubbo的SPI机制做更深入的分析。

 

以上是关于Dubbo的SPI机制的主要内容,如果未能解决你的问题,请参考以下文章

来撸一撸Dubbo之SPI机制源码,SPI到底解决了什么问题?

Dubbo 扩展点加载机制:从 Java SPI 到 Dubbo SPI

jdk和dubbo的SPI机制

Dubbo-扩展机制-SPI

《二》Dubbo核心之SPI机制

dubbo源码分析01:SPI机制