Dubbo的SPI机制的实现的源码解析(0)

Dubbo的SPI机制的实现的源码解析

java的SPI

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。典型的应用实例有jdbc的第三方实现,日志框架的实现等。

优点:

可以实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。

缺点:
  • 必须在特定的位置增加配置文件

  • ServiceLoader必须加载接口的所有的实现,获取某种实现只能遍历获取

  • ServiceLoader并非线程安全的

Spring的SPI机制

Spring有实现自己的spi机制,与java的spi有点不同。 Springboot的各个starter的引入即生效的能力就是使用的其SPI实现的。

在java包中的META-INF目录中新增spring.factories文件。 增加key=value形式的键值对, 其中key是接口的全路径类名, value是这个接口的实现类的全路径名, spring就会把文件中的实现类注入到容器中了。

通过这种方式, 第三方的jar包只要导入到classpath路径中就会自动注入到spring容器中

Dubbo的SPI机制

DUBBO也实现了自己的SPI机制,并且dubbo大量使用spi。 通过dubbo的spi可以很方便的替换dubbo中的各个组件的实现等,十分的灵活,本文主要着重的说明dubbo的spi是如何实现的。

dubbo的spi 还支持注入其他的SPI实现类的功能,提供了类似的IOC功能

使用dubbo的约束:

  1. 在jar包中如下META-INF/dubbo中增加子目录或文件,
  2. 文件名必须是接口的全路径类名
  3. 文件内容是key-value对的形式, 其中key是这个实现的名称, value是实现的类的全路径类名。

我们先看下一个DUBBO源码中的使用示例:

org.apache.dubbo.container.Main 类源码如下:

public class Main {

    public static final String CONTAINER_KEY = "dubbo.container";

    public static final String SHUTDOWN_HOOK_KEY = "dubbo.shutdown.hook";

    private static final Logger logger = LoggerFactory.getLogger(Main.class);

    //获取Container接口的所有符合的SPI中的实现类
    private static final ExtensionLoader<Container> LOADER = ExtensionLoader.getExtensionLoader(Container.class);

    private static final ReentrantLock LOCK = new ReentrantLock();

    private static final Condition STOP = LOCK.newCondition();

    // main方法中就是获取所有的符合要求的实现类,并遍历调用下, 
    public static void main(String[] args) {
        try {
            if (ArrayUtils.isEmpty(args)) {
                String config = ConfigUtils.getProperty(CONTAINER_KEY, LOADER.getDefaultExtensionName());
                args = COMMA_SPLIT_PATTERN.split(config);
            }

            final List<Container> containers = new ArrayList<Container>();
            for (int i = 0; i < args.length; i++) {
                containers.add(LOADER.getExtension(args[i]));
            }
            logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce.");

            if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) {
                Runtime.getRuntime().addShutdownHook(new Thread("dubbo-container-shutdown-hook") {
                    @Override
                    public void run() {
                        for (Container container : containers) {
                            try {
                                container.stop();
                                logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!");
                            } catch (Throwable t) {
                                logger.error(t.getMessage(), t);
                            }
                            try {
                                LOCK.lock();
                                STOP.signal();
                            } finally {
                                LOCK.unlock();
                            }
                        }
                    }
                });
            }

            for (Container container : containers) {
                container.start();
                logger.info("Dubbo " + container.getClass().getSimpleName() + " started!");
            }
            System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Dubbo service server started!");
        } catch (RuntimeException e) {
            logger.error(e.getMessage(), e);
            System.exit(1);
        }
        try {
            LOCK.lock();
            STOP.await();
        } catch (InterruptedException e) {
            logger.warn("Dubbo service server stopped, interrupted by other thread!", e);
        } finally {
            LOCK.unlock();
        }
    }
}

@SPI("spring")
public interface Container {

    /**
     * start method to load the container.
     */
    void start();

    /**
     * stop method to unload the container.
     */
    void stop();

}

ExtensionLoader

这个类是实现dubbo扩展机制的核心类。 下面讲述的源码都是这个类中的。

ExtensionLoader.getExtensionLoader(Container.class);

private static final ExtensionLoader<Container> LOADER = ExtensionLoader.getExtensionLoader(Container.class);

示例中最重要的就是这一句。 这一句就是获取到了实现类包装加载类ExtensionLoader(ExtensionLoader我称之为包装加载类)

getExtensionLoader 方法源码
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 an interface!");
    }
    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) {
       //简单的包装传入的类型,并放入缓存中。 
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

这个方法的作用主要是简单的生成ExtensionLoader类,并放入缓存中。

####ExtensionLoader类的构造方法

private ExtensionLoader(Class<?> type) {
    this.type = type;
   //这一句中会加载ExtensionFactory的spi扩展
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

在构造方法中会加载ExtensionFactory类的spi扩展, getExtensionLoader方法是一样的,我们看下getAdaptiveExtension方法。

getAdaptiveExtension
public T getAdaptiveExtension() {
    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 {
                    //如果缓存中没有这个类就会创建个
                    instance = createAdaptiveExtension();
                    cachedAdaptiveInstance.set(instance);
                } catch (Throwable t) {
                    createAdaptiveInstanceError = t;
                    throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                }
            }
        }
    }

    return (T) instance;
}

方法中都是先在缓存中寻找,如果没有就会调用createAdaptiveExtension方法,我们继续深入

private T createAdaptiveExtension() {
    try {
        //这里injectExtension会注入依赖,我们现在先看下是如何找到ExtensionFactory的spi扩展的
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
    }
}

再次深入getAdaptiveExtensionClass方法

getAdaptiveExtensionClass
private Class<?> getAdaptiveExtensionClass() {
   //这里应该会获取扩展的类
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
getExtensionClasses
private Map<String, Class<?>> getExtensionClasses() {
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
               //这里才是真正的获取扩展类
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}
loadExtensionClasses
/**
 * synchronized in getExtensionClasses
 */
private Map<String, Class<?>> loadExtensionClasses() {
    cacheDefaultExtensionName();

    Map<String, Class<?>> extensionClasses = new HashMap<>();

    for (LoadingStrategy strategy : strategies) {
        loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
        loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
    }

    return extensionClasses;
}

方法中遍历strategies集合, 使用LoadingStrategy来加载SPI扩展.

LoadingStrategy是DUBBO中的SPI扩展的扫描策略接口,dubbo中默认了四种实现, dubbo主要是使用这四种实现来加载spi扩展的

  • DubboExternalLoadingStrategy : dubbo用来加载META-INF/dubbo/external/目录下的spi扩展
  • DubboInternalLoadingStrategy : dubbo用来加载META-INF/dubbo/internal/目录下的spi扩展
  • DubboLoadingStrategy: dubbo用来加载 META-INF/dubbo/目录下的spi扩展
  • ServicesLoadingStrategy: dubbo用来加载 META-INF/services/目录下的spi扩展

我们看下具体的实现loadDirectory方法, 为何有两个方法,是因为dubbo是由阿里巴巴转给Apache社区了,转后很多的类的包名都修改成org.apache类,但是为了和以前的兼容,所以以前的包名也加载一次。

loadDirectory
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                           boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
    String fileName = dir + type;
    try {
        Enumeration<java.net.URL> urls = null;
        ClassLoader classLoader = findClassLoader();

        // try to load from ExtensionLoader's ClassLoader first
        if (extensionLoaderClassLoaderFirst) {
            ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
            if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
                urls = extensionLoaderClassLoader.getResources(fileName);
            }
        }

        if (urls == null || !urls.hasMoreElements()) {
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
        }

        if (urls != null) {
            while (urls.hasMoreElements()) {
                java.net.URL resourceURL = urls.nextElement();
                //前面的代码都是资源定位,获取资源路径的url。 这个方法才是真正的加载逻辑
                loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
            }
        }
    } catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " +
                type + ", description file: " + fileName + ").", t);
    }
}

一堆的资源加载逻辑,获取url, 真正的解析文件获取class还是在loadResource 方法中。

loadResource
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
                          java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
    try {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                final int ci = line.indexOf('#');
                if (ci >= 0) {
                    line = line.substring(0, ci);
                }
                line = line.trim();
                if (line.length() > 0) {
                    try {
                        String name = null;
                        int i = line.indexOf('=');
                        if (i > 0) {
                            name = line.substring(0, i).trim();
                            line = line.substring(i + 1).trim();
                        }
                        if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
                          //核心方法在这里 
                          loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);
                        }
                    } catch (Throwable t) {
                        IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                        exceptions.put(line, e);
                    }
                }
            }
        }
    } catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " +
                type + ", class file: " + resourceURL + ") in " + resourceURL, t);
    }
}

这个方法中会读取文件中每一行,并且每行加载一个类。 加载方法loadClass. 在调用loadClass方法的时候,在传入参数的时候其实已经已经通过Class.forName方法获取到具体的扩展实现类了。

loadClass
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
                       boolean overridden) throws NoSuchMethodException {
    if (!type.isAssignableFrom(clazz)) {
        throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                type + ", class line: " + clazz.getName() + "), class "
                + clazz.getName() + " is not subtype of interface.");
    }
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        cacheAdaptiveClass(clazz, overridden);
    } else if (isWrapperClass(clazz)) {
        cacheWrapperClass(clazz);
    } else {
        clazz.getConstructor();
        if (StringUtils.isEmpty(name)) {
            name = findAnnotationName(clazz);
            if (name.length() == 0) {
                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
            }
        }

        String[] names = NAME_SEPARATOR.split(name);
        if (ArrayUtils.isNotEmpty(names)) {
          // 放入缓存
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
              //缓存
                cacheName(clazz, n);
                saveInExtensionClass(extensionClasses, clazz, n, overridden);
            }
        }
    }
}

这个方法主要解析扩展类的name, 并且将扩展类放入缓存中。

loadClass的第一个参数extensionClasses 是从loadExtensionClasses方法就开始传入的。 主要是用来装载加载的spi类, 并且是map类型,key存的是name,在loadClass的 saveInExtensionClass 方法中就是把加载的类放入extensionClasses中。

我们回到开始getAdaptiveExtensionClass方法中

这个方法是dubbo的Adaptive机制的主要实现逻辑, 关于Adaptive机制的详情可以看下下面这篇文章:https://blog.csdn.net/weixin_33967071/article/details/92608993

Adaptive说明:如果dubbo的spi接口有多个扩展实现,那么具体使用那个扩展实现呢, 这个通过Adaptive机制就可以解决这个问题,它便没有写死使用那个扩展,而是通过你传入的URL(dubbo的)参数来动态确定你要调用的是那个扩展实现。

缺点:

  1. 实现方法必须带有URL参数。

  2. 必须在接口中增加 Adaptive注解,没有增加这个注解的方法调用都会抛出异常。

总结:

到这里就可以知道,dubbo是通过扫描所有jar包中的特定目录下的文件,根据文件名称和文件中的内容来加载扩展类的。 如果有多个扩展实现类就会都加载到缓存中,提供了多种方法可以获取到具体的实现,但是Adaptive机制才是使用来确定实现类的最多的方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值