Dubbo源码阅读——扩展机制 Dubbo SPI

一、SPI简介

一套成熟的框架在扩展性方面必然需要完备,duboo扩展点加载机制更是dubbo具有强大扩展性的一个重要因素,几乎贯穿整个Dubbo框架,Dubbo所有的扩展组件几乎都基于Dubbo SPI实现。dubbo的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。Dubbo SPI由dubbo自己实现,兼容jdk SPI,功能比jdk SPI更加强大,增加了对IoC和AOP的支持,分别体现在依赖注入和自适应扩展机制。
本文源码版本为Dubbo 2.6.5
先简单来了解下SPI机制。

1.JDK SPI

SPI是一种服务发现机制。JDK SPI通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
下面写个demo使用一下jdk spi:
定义接口 Service:

public interface Service {
    String execute();
}

两个实现类:

public class ServiceImpl1 implements Service {
    public String execute() {
        return this.getClass().getSimpleName()+"#execute()";
    }
}
public class ServiceImpl2 implements Service {
    public String execute() {
        return this.getClass().getSimpleName()+"#execute()";
    }
}

然后在classpath下新建文件夹META-INF/services,然后新建文件,文件名为接口的全限定名,内容为实现类全限定名,多个实现类用换行符分隔,
com.yozzs.test.spi.Service

com.yozzs.test.spi.impl.ServiceImpl1
com.yozzs.test.spi.impl.ServiceImpl2

然后写个测试类跑一下:

import com.yozzs.test.spi.Service;
import java.util.Iterator;
import java.util.ServiceLoader;

public class TestMain {
    public static void main(String[] args) {
        ServiceLoader<Service> loader = ServiceLoader.load(Service.class);
        Iterator<Service> iterator = loader.iterator();
        while(iterator.hasNext()){
            Service service = iterator.next();
            System.out.println(service.execute());
        }
    }
}

运行结果:

ServiceImpl1#execute()
ServiceImpl2#execute()

可见spi机制可以通过接口类型自动加载文件里定义的实现类。
Dubbo SPI与jdk SPI原理相同,功能上有一定的增强。

2.Dubbo SPI

Dubbo SPI约定,在扩展类的 jar 包内 ,放置扩展点配置文件 META-INF/dubbo/接口全限定名,或者 META-INF/dubbo/internal/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。
以扩展协议为例,dubbo支持自定义扩展协议,扩展协议需要实现org.apache.dubbo.rpc.Protocol接口。(dubbo 2.7之前是com.alibaba.dubbo.rpc.Protocol),扩展协议一般封装到jar包使用,在协议的实现 jar 包内放置文本文件:META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol,内容为:

unionpay=com.yozzs.test.rpc.protocol.Unionpay
fincontrol=com.yozzs.test.rpc.protocol.Fincontrol

这里定义了两个自定义协议,实现类需要实现Protocol接口,key就是协议命,可在dubbo配置文件标签中引用。

<dubbo:protocol name="unionpay" />

dubbo扩展点加载主要源码在ExtensionLoader类中,可以写个如下测试类去测试一下:

public class DubboSPITest {
    @Test
    public void sayHello() throws Exception {
        ExtensionLoader<Protocol> extensionLoader = 
            ExtensionLoader.getExtensionLoader(Protocol.class);
        Protocol unionpay= extensionLoader.getExtension("unionpay");
        system.out.println(unionpay.getDefaultPort());
    }
}

二、Dubbo 扩展点机制源码阅读

上面的测试代码很简单,首先通过ExtensionLoader.getExtensionLoader(type)方法获取指定接口类型的的扩展类加载器实例,然后用该实例的getExtension()方法获取接口实现类的实例。

1.ExtensionLoader的属性

首先来看看ExtensionLoader类有哪些属性:

public class ExtensionLoader<T> {

    private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
	//下面三个常量约定了dubbo spi配置文件的路径名,可以看出兼容了jdk spi
    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/";
	//配置文件内容value的多类的格式
    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
	//缓存本类的实例,getExtensionLoader方法就从这个Map里取,取不到才调用私有构造器去new
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
	//缓存扩展点接口实现类的实例
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
	//扩展点接口的类型,由getExtensionloader()方法传入
    private final Class<?> type;
	//依赖注入时调用该对象的getExtension方法获取注入所需的对象
    private final ExtensionFactory objectFactory;
	//缓存类型名称
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
	//Holder类只有一个object类型volatile属性,相当于一层包装
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
	//下面是各种各样的缓存
    private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
    private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
    private volatile Class<?> cachedAdaptiveClass = null;
    private String cachedDefaultName;
    private volatile Throwable createAdaptiveInstanceError;

    private Set<Class<?>> cachedWrapperClasses;

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

可以看出ExtensionLoader这个类的属性大多是一些缓存,后面生成的一些实例都保存在对应的缓存中,需要时先从缓存拿,拿不到再生成实例,这个过程后面多处代码会通过双重检测的方法来保证线程安全。
还有一个特别重要的属性:Class<?> type,它是ExtensionLoader要自动加载的接口类型,由用户通过参数传进来,也就是getExtensionLoader方法的参数。

2.getExtensionLoader()方法获取加载器实例

ExtensionLoader的构造器私有,对外提供getExtensionLoader()方法获取实例。代码很简单:
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!");
        }
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }

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

构造器

	private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

获取扩展类加载器代码很简单,

  1. 先做几个判断,入参非空,入参必须是接口类型,入参接口必须有@SPI注解
  2. 然后从缓存中获取实例,取不到的话调用构造器创建实例,创建实例时给type属性和objectFatory属性赋值。这里的objectFatory属性有三种实现,其中一个是适配器,准确来说是两种,后面依赖注入的时候再去看。
  3. 创建了实例要放进缓存。

获取到扩展类加载器实例就可以调用该实例的getExtension()方法获取扩展接口的实现类的实例了。

3.getExtension()方法获取扩展点的实现类实例

这个方法就是Dubbo SPI的核心功能方法,方法里有多层调用,最上面两层方法是主要逻辑。先画个图来梳理一下调用关系。

getExtension()  首先检查缓存,缓存未命中则创建拓展对象
	|
	|----> createExtension()  加载所有拓展类、创建拓展对象、依赖注入、包装成wapper对象
		|
		|----> injectExtension()  拓展对象依赖注入(setter注入)
		|
		|----> getExtensionClasses()  加载所有拓展类,从缓存中获取,若无则加载
			|
			|----> loadExtensionClasses()  判断@spi注解内容是否合法
				|
				|----> loadDirectory()  解析@spi注解,加载配置文件为url资源
					|
					|----> loadResource()  读取配置文件并解析,反射加载类,并缓存
						|
						|----> loadClass()  判断类型,分别操作缓存

上图就是Dubbo SPI加载扩展点的流程,还有一些关于自适应加载功能的代码暂且不看,下面一层一层地往下看代码:

getExtension()
	public T getExtension(String name) {//name为配置文件的key,也就是扩展点名称
        if (name == null || name.length() == 0)
            throw new IllegalArgumentException("Extension name == null");
        if ("true".equals(name)) {
        	//获取默认的扩展实现类
            return getDefaultExtension();
        }
        //Holder类只有一个object类型volatile属性,相当于一层包装
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
        	//cachedInstances是ConcurrentHashmap,putIfAbsent线程安全
            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中,缓存实例
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

这段代码比较简单,首先检查缓存,缓存没取到则创建实例,下面看创建实例的方法:

createExtension()
	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实例,然后给该实例注入依赖,
                	// 最后赋值给instance
                    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);
        }
    }

这个方法作用是创建扩展点实例,做的事情比较多。

  1. getExtensionClasses()方法,加载所有扩展类
  2. 查缓存,查不到则通过反射创建实例,并放入缓存
  3. injectExtension()方法,给创建的实例注入依赖
  4. 将实例包装为对应Wrapper对象

这里的注入依赖的方法就是Dubbo IoC的体现,查看injectExtension()方法源码可以看到依赖注入采用setter方式。
而使用Wrapper对象包装扩展实例可以实现AOP特性。举个例子,ProtocolFilterWrapper类就包装了DubboProtocol类,一些通用抽象的判断逻辑全部放到ProtocolFilterWrapper类的export()方法,该方法核心功能是调用DubboProtol类的export()方法。这里ProtocolFilterWrapper就相当于一个代理对象,对被代理的DubboProtocol对象进行功能增强。

injectExtension()
	private T injectExtension(T instance) {
        try {
        	// objectFactory属性可获取注入所需的依赖属性
            if (objectFactory != null) {
            	//遍历扩展类的方法,找到set方法
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        //@DisableInject注解表示不注入依赖
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }
                        // 依赖对象类型
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                        	// 依赖对象名
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            // 从 ObjectFactory 中获取依赖对象
                            // objectFactory是ExtensionFactory接口类型,有三种实现
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                            	// 反射调用set方法注入依赖对象
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

这是一个简单setter注入的实现,需要注意的是通过依赖对象类型和依赖对象名称从objectFactory中获取依赖对象的过程。
objectFactory是ExtensionFactory接口类型,有三种实现:

  • AdaptiveExtensionFactory
  • SpiExtensionFactory
  • SpringExtensionFactory

从ExtensionLoader的私有构造器看到,初始化ExtensionLoader实例默认objectFactory设置AdaptiveExtensionFactory类型,它是一个适配器,不是具体实现,通过AdaptiveExtensionFactory对象的getExtension()方法,会先进行判断,然后调用SpiExtensionFactorySpringExtensionFactory的同名方法去获取实例。这两种类型就是dubbo支持的原生容器方式和spring容器方式。

看完依赖注入再来看看dubbo如何加载所有扩展类的。

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()方法:

loadExtensionClasses()
    // 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));
                }
                // 缓存默认扩展名称,也就是@SPI注解的value
                if (names.length == 1) cachedDefaultName = names[0];
            }
        }

        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadDirectory(extensionClasses, DUBBO_DIRECTORY);
        loadDirectory(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

loadExtensionClasses 方法总共做了两件事情,

  1. 对 SPI 注解进行解析
  2. 调用 loadDirectory 方法加载指定文件夹配置文件。
    看loadDirectory方法如何加载配置文件指定的所有扩展类:
loadDirectory()
	private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
        String fileName = dir + type.getName();
        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 when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

这一层就是使用类加载器加载指定名字的配置文件,加载资源的方法再看loadResource()方法:

loadResource()
    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) {
                            	// 读取配置文件的key 和 value
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                            	// 加载类,并通过loadClass方法对类进行缓存
                                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);
        }
    }

loadResource() 方法用于读取配置文件并解析,并通过反射加载类。注意,这里Class.forName()方法只是生成Class<?>对象,没有初始化扩展点实例,也就是前面说的dubbo不初始化所有扩展点,只按需要初始化。然后通过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 when load extension class(interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + "is not subtype of interface.");
        }
        // 检测目标类上是否有 Adaptive 注解,该注解用于自适应加载扩展点
        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)) {// 检查clazz是否有Wrapper类型,有的话放进对应类型缓存
            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
            }
            wrappers.add(clazz);
        } else {// 普通扩展类
        	// 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
            clazz.getConstructor();
            if (name == null || name.length() == 0) {
            	// 如果 name 为空,则尝试从 Extension 注解中获取 name,或使用小写的类名作为 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 (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());
                    }
                }
            }
        }
    }

loadClass()方法主要是操作各种缓存。

三、总结

既然都是SPI的功能,Dubbo SPI的原理跟JDK SPI是差不多的,都是通过类加载器去加载并解析配置文件,加载到类型信息,然后反射生成实例,反射调用setter方法进行依赖注入,得到完整的扩展点实例。Dubbo SPI的相关主要在ExtensionLoader类里,值得注意的是该类加入了许多缓存,Class缓存和实例缓存分开,获取时并不会直接实例化所有Class,在加载过程中多处操作缓存,一定程度上可以提升性能。
本文涉及的源码只是Dubbo SPI的基本功能,Dubbo还支持许多高级特性,例如自适应扩展机制,默认使用javasisst动态编译代码,同时支持jdk编译器。后续可以深入学习一下。

发布了43 篇原创文章 · 获赞 17 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_41172473/article/details/102905655