Dubbo分析篇(1)-SPI机制

390 阅读5分钟

SPI全称为Service Provider Interface,是一种服务发现机制。SPI的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以运行时,动态为接口替换实现类。正因此特性,我们可以容易的通过SPI机制为我们的程序拓展功能。SPI机制在第三方框架中也有所应有,比如Dubbo就是通过SPI机制加载所有的组件。不过,Dubbo并未使用JAVA原生的SPI机制,而是对其进行了增强,使其能更好的满足需求。

SPI示例

前面简单介绍了SPI机制原理,本节通过一个示例演示JAVA SPI的使用方法

`

public interface Strategy {

    public void run();
}

` 接下来定义俩个实现类

`

public class People implements Strategy {

    @Override
    public void run() {
        System.out.println("people run");
    }
}

public class Animal implements Strategy {

@Override
public void run() {
    System.out.println("animal run");
}
}

` 接下来 META-INF/services 文件夹下创建一个文件,名称为 Robot 的全限定名 org.apache.spi.Robot。文件内容为实现类的全限定的类名,如下

com.example.demo.alitest.People
com.example.demo.alitest.Animal

`

@Test
public void testRun(){
    ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Strategy.class);
    System.out.println("Java SPI");
    serviceLoader.forEach(Strategy::run);
}

`

Dubbo的SPI实现

Dubbo SPI 除了支持按需加载实现类,还增加了IOC和AOP等特性,这些特性将会在接下来的源码进行分析。Dubbo所需的配置文件需放置在META-INF/dubbo路径下,配置内容如下

people = com.example.demo.alitest.People
animal = com.example.demo.alitest.Animal

Dubbo的SPI除了支持按需加载加载接口实现类,还增加了IOC和AOP特性

DubboSPI的源码分析

dubbo首先通过ExtensionLoader的getExtensionLoader方法获取一个ExtensionLoader实例,然后在通过ExtensionLoader#getExtension方法获取拓展类。

`

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) {
                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, clazz.newInstance());
        instance = (T) EXTENSION_INSTANCES.get(clazz);
    }
    // 向实例中注入依赖
    injectExtension(instance);
    Set<Class<?>> wrapperClasses = cachedWrapperClasses;
    if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
        // 循环创建 Wrapper 实例
        for (Class<?> wrapperClass : wrapperClasses) {
            // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
            // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
            instance = injectExtension(
                (T) wrapperClass.getConstructor(type).newInstance(instance));
        }
    }
    return instance;
} catch (Throwable t) {
    throw new IllegalStateException("...");
}

} `

以上实现步骤
①、通过getExtensionClasses获取所有的类的Clazz对象
②、通过反射创建拓展对象
③、向拓展对象注入依赖
④、将拓展对象包裹在相应的 Wrapper 对象中

获取所有拓展类

我们在通过名称获取拓展类之前,首先需要根据配置文件解析出拓展项名称到拓展类的映射关系表(Map<名称, 拓展类>),之后再根据拓展项名称从映射关系表中取出相应的拓展类即可。相关过程的代码分析如下:

`

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

` 这里先去缓存中去获取,然后在进行非空判断,为空的情况下,通过synchronized的关键字来进行锁住,在次去缓存中获取,依旧为null,则通过loadExtensionClasses加载拓展类。下面对loadExtensionClasses方法进行分析。

`

private Map<String, Class<?>> loadExtensionClasses() {
    cacheDefaultExtensionName();

    Map<String, Class<?>> extensionClasses = new HashMap<>();
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    return extensionClasses;
}

` loadExtensionClasses总共做了俩件事情,①、对SPI注解进行加载。②、调用loadDirectory方法加载指定文件夹配置文件,接下来我们来看看loadDirectory方法做了哪些事情。

`

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
    String fileName = dir + type;
    try {
        Enumeration<java.net.URL> urls;
        ClassLoader classLoader = findClassLoader();
        if (classLoader != null) {
            urls = classLoader.getResources(fileName);
        } else {
            urls = ClassLoader.getSystemResources(fileName);
        }
        if (urls != null) {
            while (urls.hasMoreElements()) {
                java.net.URL resourceURL = urls.nextElement();
                loadResource(extensionClasses, classLoader, resourceURL);
            }
        }
    } catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " +
                type + ", description file: " + fileName + ").", t);
    }
}

`

loadDirectory 方法通过classloader获取所有资源链接,然后通过loadResource方法加载资源。

`

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
    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) {
                            loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                        }
                    } 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);
    }
}

loadResource 方法用于读取和解析配置文件,并通过反射加载类,最后调用 loadClass 方法进行其他操作。loadClass 方法用于主要用于操作缓存,该方法的逻辑如下:

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) 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);
    } 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);
            }
        }
    }
}

` loadClass底层是采用反射的原理来进行加载类。
本篇文章是参考dubbo源码分析文档。
dubbo的源码文档地址:dubbo.apache.org/zh-cn/docs/…