Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

# 聊聊 Dubbo 的 SPI

发表于 2020-05-31 更新于 2022-04-02 分类于 Java ， Dubbo ， RPC ， SPI ， Dubbo ， SPI 阅读次数： 阅读次数： Disqus：

SPI全称是Service Provider Interface，咋眼看跟api是不是有点相似，api是application interface，这两个其实在某些方面有类似的地方，也有蛮大的区别，比如我们基于 dubbo 的微服务，一般我们可以提供服务，然后非泛化调用的话，我们可以把 api 包提供给应用调用方，他们根据接口签名传对应参数并配置好对应的服务发现如 zk 等就可以调用我们的服务了，然后 spi 会有点类似但是是反过来的关系，相当于是一种规范，比如我约定完成这个功能需要两个有两个接口，一个是连接的，一个是断开的，其实就可以用 jdbc 的驱动举例，比较老套了，然后各个厂家去做具体的实现吧，到时候根据我接口的全限定名的文件来加载实际的实现类，然后运行的时候调用对应实现类的方法就完了

看上面的图，`java.sql.Driver`就是 spi，对应在classpath 的 META-INF/services 目录下的这个文件，里边的内容就是具体的实现类

简单介绍了 Java的 SPI，再来说说 dubbo 的，dubbo 中为啥要用 SPI 呢，主要是为了框架的可扩展性和性能方面的考虑，比如协议层 dubbo 默认使用 dubbo 协议，同时也支持很多其他协议，也支持用户自己实现协议，那么跟 Java 的 SPI 会有什么区别呢，我们也来看个文件

是不是看着很想，又有点不一样，在 Java 的 SPI 配置文件里每一行只有一个实现类的全限定名，在 Dubbo的 SPI配置文件中是 key=value 的形式，我们只需要对应的 key 就能加载对应的实现，

    /**
         * 返回指定名字的扩展。如果指定名字的扩展不存在，则抛异常 {@link IllegalStateException}.
         *
         * @param name
         * @return
         */
    	@SuppressWarnings("unchecked")
    	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;
    	}

这里其实就可以看出来第二个不同点了，就是这个`cachedInstances`，第一个是不用像 Java 原生的 SPI 那样去遍历加载对应的服务类，只需要通过 key 去寻找，并且寻找的时候会先从缓存的对象里去取，还有就是注意下这里的 DCL(double check lock)

    @SuppressWarnings("unchecked")
        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);
                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);
            }
        }

然后就是创建扩展了，这里如果 wrapperClasses 就会遍历生成wrapper实例，并做 setter 依赖注入，但是这里cachedWrapperClasses的来源还是有点搞不清楚，得再看下 com.alibaba.dubbo.common.extension.ExtensionLoader#loadFile的具体逻辑
又看了遍新的代码，这个函数被抽出来了

    /**
        * test if clazz is a wrapper class
        * <p>
        * which has Constructor with given class type as its only argument
        */
       private boolean isWrapperClass(Class<?> clazz) {
           try {
               clazz.getConstructor(type);
               return true;
           } catch (NoSuchMethodException e) {
               return false;
           }
       }

是否是 wrapperClass 其实就看构造函数的。