Dubbo SPI 工作原理分析 (1)

71 阅读8分钟

在上一篇文章中大概分析了一下Java SPI 的工作原理与源码,现在进入真正的主题DUBBO SPI的原理和源码的学习。

PS: 使用的源码版本为dubbo github上 2.6.x

老规矩,我们先来看一下DUBBO SPI的使用步骤:

  1. 定义一个接口,并在接口上使用 @SPI 注解,来声明这个接口是一个扩展点。例如:
@SPI
public interface Foo {
  // ...
}

  1. 定义接口的实现类
public class FooImpl implements Foo {
  // ...
}

3.在 jar 包的META-INF/dubbo下面创建一个接口全限定名的文件 xxx.xxx.Foo, 接口全限定名文件中以键值对的形式填写实现类 hello=xxx.xxx.FooImpl

4.在需要使用这个扩展点的地方,调用 ExtensionLoader.getExtensionLoader(Foo.class).getExtension(...) 方法,来获取指定的扩展点实现。例如:

Foo foo = ExtensionLoader.getExtensionLoader(Foo.class).getExtension("fooImpl");

以上就是DUBBO SPI 的基本用法和步骤,现在我们来看一下它的工作原理。

Dubbo SPI 工作原理分析

Foo foo = ExtensionLoader.getExtensionLoader(Foo.class).getExtension("fooImpl");

以上这行代码中的ExtensionLoader类就是Dubbo SPI中的核心类,ExtensionLoader中包含了四大核心功能点:

  1. 扩展点的加载
  2. 扩展点的依赖注入
  3. 扩展点的自适应
  4. 扩展点的激活

正所谓知己知彼,百战不殆。在我们分析这些功能之前,我们先来看看ExtensionLoader类的一肚子“坏水”中,都有哪些东西。

ExtensionLoader类的成员变量

我直接把代码贴上来,上面我都加了注解

public class ExtensionLoader<T> {

    private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
    //1、配置类 与ServiceLoader的加载目录相同,这说明 Dubbo 也会加载 Java SPI 目录里的扩展配置。
    private static final String SERVICES_DIRECTORY = "META-INF/services/";
    //1、配置类 用户扩展点加载目录,一般在二次开发扩展时都放在这个目录里面
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
    //1、配置类 Dubbo 内部扩展点加载目录,Dubbo 内部实现的扩展点配置文件都放在这个目录下。
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");

    //2、全局容器类
    //是ExtensionLoader的容器,保存所有的ExtensionLoader对象,key= 扩展点类型,value= ExtensionLoader对象
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
    //扩展点实例容器,保存了所有实例化后的扩展点对象,key= 扩展点 Class,value= 扩展点对象
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();

    // ==============================
    //扩展点类型,Dubbo SPI要求这里的扩展点类型必须是接口。
    private final Class<?> type;

    //扩展点对象工厂,用于依赖注入时,获取依赖的扩展点对象。
    //ExtensionLoader<ExtensionFactory>的objectFactory为null,其他所有的ExtensionLoader的objectFactory都是AdaptiveExtensionFactory。
    //AdaptiveExtensionFactory里面是对ExtensionFactory的包装,包含了SpiExtensionFactory和SpringExtensionFactory。
    //AdaptiveExtensionFactory也可以说是获取扩展点的一层代理,底层都是从SpiExtensionFactory和SpringExtensionFactory获取扩展点。
    private final ExtensionFactory objectFactory;

    //功能工具类主要是作为工具用在逻辑处理中,包括cachedNames、cachedClasses、cachedActivates。
    //扩展点类型-扩展点定义 key 的映射关系容器,可以通过扩展点 Class 获取 key
    ////如:"class org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol" -> dubbo
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();

    //扩展点定义 key- 扩展点类型的映射关系容器,可以通过 key 获取扩展点 Class
    //dubbo -> "class org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol"
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();

    //激活的扩展点容器,key= 扩展点定义 key,value=@Activate对象
    ////如:token -> {$Proxy7@2886} "@org.apache.dubbo.common.extension.Activate(after=[], value=[token], before=[], group=[provider], order=0)"
    private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();

    //局部容器类 主要用于保存当前扩展点ExtensionLoader里的数据。
    //包括cachedInstances、cachedAdaptiveInstance和cachedAdaptiveClass。
    //扩展点对象Holder的容器,用于保存已经实例化的扩展点对象Holder
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();

    //自适应扩展点对象持有者,持有了 Dubbo 生成的自适应扩展点对象
    private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();

    //自适应扩展点 Class 持有者,持有了 Dubbo 生成的自适应扩展点类。
    private volatile Class<?> cachedAdaptiveClass = null;
    //默认扩展点key,@SPI注解里的value
    private String cachedDefaultName;
    private volatile Throwable createAdaptiveInstanceError;

    private Set<Class<?>> cachedWrapperClasses;

    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();

以上就是ExtensionLoader 中的比较重要的成员变量,详细可以看注释。

ExtensionLoader中还有几个比较重要的方法,涵盖了其核心功能点:

  1. getExtensionLoader(Class class):获取ExtensionLoader对象。
  2. getExtension(String key):根据key获取扩展点。
  3. getAdaptiveExtension():获取自适应扩展点。
  4. getActivateExtension(URL, String, String):获取激活的扩展点集合。

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 interface!");
        }
        //接口必须有@SPI 的注解
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }
        //根据扩展点类型获取ExtensionLoader。
        //EXTENSION_LOADERS是保存ExtensionLoader对象的容器Map,key=扩展点类,value=ExtensionLoader对象
        //EXTENSION_LOADERS的定义:ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>()
        //由此可知,每种扩展点都拥有一个ExtensionLoader,全都保存在EXTENSION_LOADERS里。
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            //实例化ExtensionLoader,并保存到EXTENSION_LOADERS里
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
    
    private ExtensionLoader(Class<?> type) {
        this.type = type;
        //初始化objectFactory,这里主要用于依赖注入
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

通过上面的代码,可以看到从EXTENSION_LOADERS中获取对应的 ExtensionLoader对象,如果没有则初始化一个新的放到map中去,构造方法中就是简单的两行代码,objectFactory的初始化我们后面详细说。至此ExtensionLoader 对象就已经初始化完成了。

SPI 扩展点的获取实现

我们现在来看是如何获取扩展点的

//ExtensionLoader#getExtension
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中获取扩展点Holder,如果没有就创建一个新的Holder
            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;
    }

上面最重要的就是 方法createExtension(name) 接着往里面走:

//com.alibaba.dubbo.common.extension.ExtensionLoader#createExtension
private T createExtension(String name) {
        //获取扩展点Class,
        //此处的getExtensionClasses()是关键,包含了解析配置文件、加载类等逻辑。
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            //先从全局扩展点容器EXTENSION_INSTANCES里获取,如果没有就创建个新的
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //依赖注入,这里是依赖注入的关键代码,下面会单独解释。
            injectExtension(instance);
            //如果包含包装类,则返回包装类。
            //备注:所谓包装类,就是对原类的增强类,类名为XXXWrapper、且有一个XXXWrapper(XXX)的构造函数。如ProtocolFilterWrapper
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                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<?>> getExtensionClasses() {
        //cachedClasses是扩展点名-扩展点Class的Map容器的Holder
        Map<String, Class<?>> classes = cachedClasses.get();
        //同步处理,防止重复加载扩展点Class
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    //加载扩展点Class
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }
   
    // synchronized in getExtensionClasses
    private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if ((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<扩展点名,扩展点Class>
        //这里需要注意的是,以下三个目录中的配置文件都会加载进去:
        //1、META-INF/services/;2、META-INF/dubbo/;3、META-INF/dubbo/internal/
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadDirectory(extensionClasses, DUBBO_DIRECTORY);
        loadDirectory(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

从上面的源码中,我们终于可以看到loadDirectory() 方法加载了三个配置文件的路径:

//1、META-INF/services/;2、META-INF/dubbo/;3、META-INF/dubbo/internal/ 到这里已经可以推断出,扩展点的加载都是在这里完成的,我们继续跟进去。

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
        //拼接文件名,如META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol
        String fileName = dir + type.getName();
        try {
            //初始化ClassLoader
            //获取所有配置文件路径,这里注意可能存在多个文件,所以可能存在多个URL
            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();
                    //解析文件,并加载扩展点Class
                    //解析过程简单来说就是
                    // 1、逐行读取文件
                    // 2、解析出key和Class
                    // 3、加载Class,并保存到各个缓存容器里,如cacheNames、cachedAdaptiveClass等
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }
    
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
            try {
                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);
                        }
                    }
                }
            } finally {
                reader.close();
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }
    
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 when load extension class(interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + "is not subtype of interface.");
        }
        //看是否是自适应类
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            if (cachedAdaptiveClass == null) {
                cachedAdaptiveClass = clazz;
            } else if (!cachedAdaptiveClass.equals(clazz)) {
                throw new IllegalStateException("More than 1 adaptive class found: "
                        + cachedAdaptiveClass.getClass().getName()
                        + ", " + clazz.getClass().getName());
            }
        //是否是包装类
        } else if (isWrapperClass(clazz)) {
            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
            }
            wrappers.add(clazz);
        } else {
            clazz.getConstructor();
            if (name == null || name.length() == 0) {
                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 (names != null && names.length > 0) {
                Activate activate = clazz.getAnnotation(Activate.class);
                if (activate != null) {
                    cachedActivates.put(names[0], activate);
                }
                for (String n : names) {
                    if (!cachedNames.containsKey(clazz)) {
                        cachedNames.put(clazz, n);
                    }
                    Class<?> c = extensionClasses.get(n);
                    if (c == null) {
                        extensionClasses.put(n, clazz);
                    } else if (c != clazz) {
                        throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                    }
                }
            }
        }
    }

从上面的代码总结出大概的流程就是解析文件,并加载扩展点Class 解析过程做了这几个步骤:

  1. 逐行读取文件
  2. 解析出key和Class
  3. 加载Class,并保存到各个缓存容器里,如cacheNames、cachedAdaptiveClass等

至此,扩张点的加载就已经完成了。下一篇继续学习后面的核心功能~