dubbo source code series 2-Dubbo SPI source code

Introduction

SPI is called Service Provider Interface, which is a service discovery mechanism. The essence of SPI is to configure the fully qualified name of the interface implementation class in a file, and the service loader reads the configuration file and loads the implementation class. This can dynamically replace the implementation class for the interface at runtime . Because of this feature, we can easily provide extended functions for our programs through the SPI mechanism. The SPI mechanism is also used in third-party frameworks. For example, Dubbo loads all components through the SPI mechanism. However, Dubbo did not use Java's native SPI mechanism, but enhanced it to better meet its needs. In Dubbo, SPI is a very important module. Based on SPI, we can easily expand Dubbo. If you want to learn Dubbo's source code, you must understand the SPI mechanism. Next, let's first understand the usage of Java SPI and Dubbo SPI, and then analyze the source code of Dubbo SPI

Dubbo SPI source code

The dubbo SPI first obtains an ExtensionLoader instance through the getExtensionLoader method of ExtensionLoader, and then obtains the extension class object through the getExtension method of ExtensionLoader . Among them, the getExtensionLoader method is used to obtain the ExtensionLoader corresponding to the extension class from the cache. If the cache is missed, a new instance is created. Let's start analyzing the source code from the unit test of the dubbo source code, the entrance is as follows:

Take a brief look at the ExtensionLoader class, the source code is as follows:

The core of ExtensionLoader is to load all the classes in the three directories of your own project META-INF/services/, META-INF/dubbo/, META-INF/dubbo/internal . This is also a pavement for the subsequent loading of your own extension classes. Your own extension classes only need to be placed in any of these three directories and can be directly loaded into the spring container.

Next, let’s look at the getExtensionLoader method in ExtensionLoader. There is no complicated logic in this method, but an instance of ExtensionLoader is obtained, as shown in the following figure:

The core method of loading classes is the getExtension method of ExtensionLoader, the code is as follows:

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

What is the process of creating the createExtension method? Let's take a look at the code:

private T createExtension(String name) {
        // 从配置文件中获取所有的配置类,得到键值对(key:配置项名称,value:配置类),即配置项名称和配置类的映射关系
        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 (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    // 将当前instance实例作为参数传给 wrapper 的构造方法,然后向 wrapper 中注入依赖,最后再赋值给 instance 变量
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

The createExtension method contains the following four steps:

1. Get all extension classes through getExtensionClasses

2. Create extended objects through reflection

3. Inject dependencies into the extension object

4. Wrap the extension object in the corresponding Wrapper object

Among the above steps, the first step is the key to loading extended classes, and the third and fourth steps are the concrete realization of Dubbo IOC and AOP. Next, we will focus on analyzing the logic of the getExtensionClasses method, and briefly introduce the specific implementation of Dubbo IOC

Get all extension classes in the specified folder

Before we get the extension class by name, we first need to parse out the mapping relationship table (Map<name, extension class>) from the extension item name to the extension class according to the configuration file, and then take out the corresponding mapping relationship table according to the extension item name Just expand the class. The code of the relevant process is as follows:

 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 in getExtensionClasses
    private Map<String, Class<?>> loadExtensionClasses() {
        // 缓存spi注解上的默认扩展类名称
        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;
    }

    /**
     * extract and cache default extension name if exists
     */
    private void cacheDefaultExtensionName() {
        // 获取spi注解,这里的 type 是 getExtensionLoader 传入的
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            // spi注解value值
            String value = defaultAnnotation.value();
            if ((value = value.trim()).length() > 0) {
                // 以“,”分隔value值,多个值会报错
                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];
                }
            }
        }
    }

The loadExtensionClasses method mainly does two things:

1. Parse the spi annotations and get the default extension class name

2. Load the configuration extension class under the specified file

How does the loadDirectory method load the configuration extension class? The code is as follows:

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        // 具体的文件路径,例如 dir(META-INF/dubbo/internal/) + type (org.apache.dubbo.container.Container) =
        // META-INF/dubbo/internal/org.apache.dubbo.container.Container
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls;
            ClassLoader classLoader = findClassLoader();
            // 根据文件名加载所有的同名文件
            if (classLoader != null) {
                // classLoader获取资源链接
                urls = classLoader.getResources(fileName);
            } else {
                // classLoader获取资源链接
                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);
        }
    }

In the loadDirectory method, the resource link is obtained through classLoader, and then the loadResource method is called to load the resource file. The loadResource method code is as follows:

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) {
                                // 通过反射 Class.forName 方法加载类,然后调用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);
                        }
                    }
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }

loadResource first reads the content of the configuration file, then loads the class through reflection, and finally calls the loadClass method to operate the cache of the class. The loadClass method code is as follows:

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)) {
            // 类是否有Adaptive注解,设置AdaptiveClass缓存
            cacheAdaptiveClass(clazz);
        } else if (isWrapperClass(clazz)) {
            // 类是否是 Wrapper类型,设置WrapperClass缓存
            cacheWrapperClass(clazz);
        } else {
            // 普通的扩展类
            // 获取无参构造方法
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                // name为空时则从 Extension 注解中过去,注解还获取不到的话,则使用小写的类名作为名称
                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)) {
                // 类上是否有 Activate 注解,如果有缓存下来
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    // 存储类和类名称映射关系到map中
                    cacheName(clazz, n);
                    // 存储类名称和类的映射关系
                    saveInExtensionClass(extensionClasses, clazz, n);
                }
            }
        }
    }


    @SuppressWarnings("deprecation")
    private String findAnnotationName(Class<?> clazz) {
        // 获取Extension注解
        org.apache.dubbo.common.Extension extension = clazz.getAnnotation(org.apache.dubbo.common.Extension.class);
        if (extension == null) {
            // 类名小写作为name
            String name = clazz.getSimpleName();
            if (name.endsWith(type.getSimpleName())) {
                name = name.substring(0, name.length() - type.getSimpleName().length());
            }
            return name.toLowerCase();
        }
        // 返回Extension注解value作为name
        return extension.value();
    }


The loadClass method logic is also relatively simple, there are two main operations:

1. Set up AdaptiveClass cache, WrapperClass cache, ActivateClass cache

2. It stores the mapping relationship between the class and the class name, and the mapping relationship between the class name and the class

Dubbo IOC

Dubbo IOC injects dependencies through setter methods . Dubbo first obtains all the methods of the instance through reflection, and then traverses the method list to check whether the method name has setter method characteristics. If so, obtain the dependent object through ObjectFactory, and finally set the dependent object to the target object by calling the setter method through reflection. The code corresponding to the whole process is as follows:

private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    // 获取实例的所有setter方法
                    if (isSetter(method)) {
                        /**
                         * Check {@link DisableInject} to see if we need auto injection for this property
                         */
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }
                        // 获取方法参数类型
                        Class<?> pt = method.getParameterTypes()[0];
                        if (ReflectUtils.isPrimitives(pt)) {
                            continue;
                        }
                        try {
                            // 获取方法对应的属性名,例如 setName 对应属性名 name
                            String property = getSetterProperty(method);
                            // 从 objectFactory 中获取依赖对象
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                // 通过反射调用 setter 方法设置依赖
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("Failed to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }


    /**
     * return true if and only if:
     * <p>
     * 1, public
     * <p>
     * 2, name starts with "set"
     * <p>
     * 3, only has one parameter
     */
    private boolean isSetter(Method method) {
        // 方法是 set 开头,public 类型 且只有一个参数
        return method.getName().startsWith("set")
                && method.getParameterTypes().length == 1
                && Modifier.isPublic(method.getModifiers());
    }

The type of the above objectFactory variable is AdaptiveExtensionFactory, and AdaptiveExtensionFactory internally maintains an ExtensionFactory list for storing other types of ExtensionFactory. Dubbo currently provides two ExtensionFactory, namely SpiExtensionFactory and SpringExtensionFactory.

AdaptiveExtensionFactory: ExtensionFactory list, including SpiExtensionFactory and SpringExtensionFactory

SpiExtensionFactory: Create an adaptive extension class

SpringExtensionFactory: Used to obtain the required extension classes from Spring's IOC container

Dubbo IOC currently only supports setter injection

dubbo SPI example demo

1. Create a DemoContainer file in the org.apache.dubbo.container.spring directory, as shown in the figure:

code show as below:

package org.apache.dubbo.container.spring;

import org.apache.dubbo.container.Container;

/**
 * 测试类
 *
 * @date 2019-10-21 14:46
 **/
public class DemoContainer implements Container {
    @Override
    public void start() {
        System.out.println("start method is run");
    }

    @Override
    public void stop() {
        System.out.println("stop method is run");
    }
}

2. Add demo=org.apache.dubbo.container.spring.DemoContainer to the current org.apache.dubbo.container.Container file under META-INF/dubbo/internal, as shown in the following figure:

3. Add test demo, the code is as follows:

package org.apache.dubbo.container.spring;

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

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

/**
 * StandaloneContainerTest
 */
public class SpringContainerTest {

    @Test
    public void testDemo(){
        DemoContainer demoContainer = (DemoContainer) ExtensionLoader.getExtensionLoader(Container.class).getExtension("demo");
        demoContainer.start();
        demoContainer.stop();
    }

}

The results of the operation are as follows:

reference:

https://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html

Guess you like

Origin blog.csdn.net/ywlmsm1224811/article/details/102663169