一篇短文就能搞定Dubbo SPI 源码及示例

SPI的介绍请参考上篇博客:Dubbo SPI前篇——Java SPI源码及示例,本文主要介绍Dubbo的SPI实现(基于源码2.7.7)。

1. Dubbo SPI 简介

Dubbo 并未使用 Java 原生的 SPI 机制,而是重新实现了一套功能更强的 SPI 机制,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法。

2. Dubbo SPI 示例

2.1 创建示例maven工程

在这里插入图片描述

2.2 编写SPI接口及实现(一般是外部使用人员扩展实现)
package com.qqxhb.spi.dubbo;

import org.apache.dubbo.common.extension.SPI;

@SPI("dog")//必须要加的注解,标识ݷ为Dubbo SPI,属性值用于指定默认的扩展点名称
public interface IAnimal {
	void say();
}
package com.qqxhb.spi.dubbo;

public class Cat implements IAnimal {

	@Override
	public void say() {
		System.out.println("猫叫========");
	}
}
package com.qqxhb.spi.dubbo;
public class Dog implements IAnimal {

	@Override
	public void say() {
		System.out.println("狗吠========");
	}
}
2.3 编写接口配置文件

在classpath下创建META-INF/dubbo文件夹(也可以是META-INF/services和META-INF/dubbo/internal,当然还可以自定义实现,见下源码分析4),在文件夹下创建具体接口全限定名文件,在文件中配置接口的具体实现(类全限定名)。本例com.qqxhb.spi.dubbo.IAnimal文件内容如下:

cat=com.qqxhb.spi.dubbo.Cat
dog=com.qqxhb.spi.dubbo.Dog

2.4 使用ExtensionLoader加载实现类并调用
package com.qqxhb.spi.dubbo;

import org.apache.dubbo.common.extension.ExtensionLoader;

public class DubboSPI {

	public static void main(String[] args) {
		// 根据扩展类型实例化 ExtensionLoader
		ExtensionLoader<IAnimal> extensionLoader = ExtensionLoader.getExtensionLoader(IAnimal.class);
		// 获取SPI注解中指定的默认扩展实现
		extensionLoader.getDefaultExtension().say();
		// 根据配置文件中的key获取对应的扩展
		extensionLoader.getExtension("dog").say();
		extensionLoader.getExtension("cat").say();
	}

}

调用成功,结果在控制台打印:

狗吠========
狗吠========
猫叫========

3. Dubbo SPI 加载源码分析

  1. 根据示例代码可知,首先调用getExtensionLoader 方法获取一个 ExtensionLoader 实例,该方法用于从缓存中获取与拓展类对应的 ExtensionLoader,若缓存未命中,则创建一个新的实例。创建实例也就算对ExtensionFactory和type属性进行赋值。
    private static <T> boolean withExtensionAnnotation(Class<T> type) {
        return type.isAnnotationPresent(SPI.class);
    }

    @SuppressWarnings("unchecked")
    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!");
        }
        //接口必须有@SPI注解
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type +
                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }
        //从ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS中取,取不到则创建一个新的实例 
        return (ExtensionLoader<T>) EXTENSION_LOADERS.computeIfAbsent(type, k -> new ExtensionLoader<T>(type));
    }
    
      
 //私有化构造函数
private ExtensionLoader(Class<?> type) {
        this.type = type;
        //通过SPI的方式获取ExtensionFactory的实例
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
  1. 有上面可以看到第一步只是获取了ExtensionLoader实例,并没SPI的主要逻辑,因此我们接着看org.apache.dubbo.common.extension.ExtensionLoader.getExtension(String)方法的源码
    /**
     * 根据名称获取扩展实例
     * @param name
     * @return
     */
    @SuppressWarnings("unchecked")
    public T getExtension(String name) {
    	//名称必传,即配置文件中的key
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        //如果传true则会获取默认扩展,即SPI注解中指定的扩展
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        //根据名称获取实例持有对象(ConcurrentMap<String, Holder<Object>> cachedInstances),该对象内部就一个属性即具体的实例对象,
        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;
    }
  1. 紧跟上一步源码,可以看到主要的逻辑在createExtension方法中,接下来就查看org.apache.dubbo.common.extension.ExtensionLoader.createExtension(String)方法源码
 /**
     * 创建 扩展实例
     * @param name
     * @return
     */
    @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, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            /**
             * 下面是dubbo 的IOC和AOP实现原理
             */
            //依赖注入(setter方法)
            injectExtension(instance);
            // 如果存在包装了则,循环进行包装类的注入
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                	//构造函数存在type类型的参数则认为是包装类,将上一个包装类作为实例注入到下一个包装类
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

  1. 由上一步源码可知,在通过名称获取拓展类之前,首先需要根据配置文件解析出拓展项名称到拓展类的映射关系表(Map<名称, 拓展类>),之后再根据拓展项名称从映射关系表中取出相应的拓展类即可。
/**
     * 获取所有的扩展类
     * @return
     */
    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;
    }
 /**
     * 加载扩展配置
     * */
    private Map<String, Class<?>> loadExtensionClasses() {
    	//缓存默认扩展名称即SPI注解指定的名称
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();
        // 加载指定文件夹下的配置文件,默认的不同策略及对应了不同的名称
        for (LoadingStrategy strategy : strategies) {
            loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
            loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
        }

        return extensionClasses;
    }

上面遍历的多个策略,实际就是多个配置文件路径,也可以自定义

 //默认三个扩展配置路径
private static final String SERVICES_DIRECTORY = "META-INF/services/";

private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";

private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
/**
* 默认的几个策略实现
*/
private static LoadingStrategy DUBBO_INTERNAL_STRATEGY =  () -> DUBBO_INTERNAL_DIRECTORY;
private static LoadingStrategy DUBBO_STRATEGY = () -> DUBBO_DIRECTORY;
private static LoadingStrategy SERVICES_STRATEGY = () -> SERVICES_DIRECTORY;

private static LoadingStrategy[] strategies = new LoadingStrategy[] { DUBBO_INTERNAL_STRATEGY, DUBBO_STRATEGY, SERVICES_STRATEGY };
 /**
 * 可以自定义实现
* @param strategies
*/
public static void setLoadingStrategies(LoadingStrategy... strategies) {
     ExtensionLoader.strategies = strategies;
}
  1. 根据加载扩展的源码可知,扩展实现是通过loadDirectory方法加载的,因此接下来看此方法的源码org.apache.dubbo.common.extension.ExtensionLoader.loadDirectory(Map<String, Class<?>>, String, String, boolean, String...)
/**
     * 加载指定路径下的扩展实现
     * @param extensionClasses
     * @param dir
     * @param type
     * @param extensionLoaderClassLoaderFirst
     * @param excludedPackages
     */
    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                               boolean extensionLoaderClassLoaderFirst, String... excludedPackages) {
        //文件名称 路径+type(接口全限定名)
    	String fileName = dir + type;
        try {
        	//所有的资源都封装成URL
            Enumeration<java.net.URL> urls = null;
            //获取线程上下文类加载器,如果没有则使用加载ExtensionLoader的类加载器
            ClassLoader classLoader = findClassLoader();
            /*
             * 通过getResources加载所有的同名文件资源
             */
            
            // 首先尝试从扩展加载程序的类加载程序加载,
            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, excludedPackages);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

6.接着上面的源码,分析loadResource方法的源码

 private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
                              java.net.URL resourceURL, 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);
                            }
                        } 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);
        }
    }
  1. loadResource 源码首先就是按行读取,接着调用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.");
        }
    	// 检测目标类上是否有 Adaptive 注解,有则缓存到AdaptiveClass
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz);
         //判断是不是包装类(看是否存在以当前type(接口)未参数的构造函数),是则缓存到包装类中
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
        	// 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
            clazz.getConstructor();
            //如果没哟设置名称,则获取Extension注解值作为名称,若也没有,则使用类名小写做为名称
            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);
                }
            }
            //根据"\\s*[,]+\\s*"切分名称
            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
            	// 如果类上有 Activate 注解,则使用 names 数组的第一个元素作为键,
                // 存储 name 到 Activate 注解对象的映射关系
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                	// 存储 Class 到名称的映射关系
                    cacheName(clazz, n);
                    //名称到Class的映射
                    saveInExtensionClass(extensionClasses, clazz, n);
                }
            }
        }
    }

到这里SPI核心的源码就分结束了,有一些小的细节可以自行查看dubbo源码中的common模块:https://github.com/qqxhb/dubbo/tree/master/dubbo-common
示例源码:https://github.com/qqxhb/dubbo-demo/tree/master/dubbo-source-code-demo
下篇相关博客:一文彻底理解Dubbo SPI 自适应(Adaptive)拓展原理

发布了131 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43792385/article/details/105290724
今日推荐