Dubbo SPI mechanism of Dubbo source reading series

Time:2020-3-28

Recently, I took the time to start reading the Dubbo source code. I hope that I can record and share my understanding of Dubbo by writing an article. If there are some mistakes in this article, I hope you will point out.

Dubbo SPI introduction

Java SPI

Before reading this article, you may need to have a simple understanding of the Java SPI (service provider interface) mechanism. Here is a brief introduction: in the object-oriented design, we advocate the interface based programming between modules. Different modules may have different specific implementations, but in order to avoid excessive coupling between modules, we need an effective service (service implementation) discovery mechanism to select specific modules. SPI is such a scheme based on interface programming + policy mode + configuration file, which can be used by users to enable / replace modules according to their actual needs.

The improvement of Dubbo SPI

The following is excerpted from https://dubbo.gitbooks.io/dub
DubboExtension pointThe load is enhanced from the SPI (service provider interface) extension point discovery mechanism of the JDK standard.
The spi of the JDK standard instantiates all the implementations of extension points at one time. If there is an extension implementation initialization, it is time-consuming, but if it is not used, it will also load, which will waste resources.
If the extension point fails to load, the name of the extension point cannot be obtained. For example: the JDK standard scriptengine gets the name of the script type through getname(), but if rubyscriptengine fails to load the rubyscriptengine class because the jruby.jar it depends on does not exist, the failure reason is eaten, which does not correspond to ruby. When users execute Ruby scripts, they will report that they do not support Ruby, rather than the real failure reason.
Support for IOC and AOP is added. One extension point can be directly set to inject other extension points

In Dubbo, if an interface is marked with @ SPI annotation, we think it is an extension point in Dubbo. Extension point is the core of Dubbo SPI. Next, we will talk about the implementation of extension point loading, automatic packaging and automatic assembly.

Detailed explanation of Dubbo SPI mechanism

Loading Dubbo extension points

Before reading this article, if you have read Java SPI, you may recall that there is a directory like / meta-inf / services. In this directory, there is a file named after the interface. The content of the file is the fully qualified name of the interface specific implementation class. We can find similar designs in Dubbo.

  • Meta-inf / services / (Java SPI compatible)
  • Meta-inf / Dubbo / (implementation of custom extension point)
  • Meta-inf / Dubbo / internal / (Dubbo internal extension point implementation)

Very good ~ now that we know where to load extension points, let’s recall how Java SPI is loaded.

ServiceLoader<DubboService> spiLoader = ServiceLoader.load(XXX.class);

Similarly, in Dubbo, there is an extensionloader class for loading extension points. In this chapter, we will focus on how this class helps us load extension points. Let’s start with a short piece of code.

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

A lot of similar code fragments are used in Dubbo’s implementation. We only need to provide a type to obtain the type’s adaptive (the understanding of adaptive will be mentioned later) extension class. When getting the corresponding adaptive extension class, we first get the extensionloader of this type. Seeing this, we should subconsciously feel that for each type, there should be a corresponding extensionloader object. Let’s first see how the extensionloader is obtained.

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!");
    }
    //Is it identified by SPI annotation 
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type(" + type +
                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    }
    //Extension_loaders is a concurrentmap set, key is a class object, and value is an extender object
    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());
}

The code in the above section is relatively simple. According to the type, get the loader from the extension \ loaders collection. If the returned value is null, create a new extensionloader object. Similar methods are used to obtain the objectfactory. The extended adaptive class of extensionfactory is obtained.

getAdaptiveExtension()

public T getAdaptiveExtension() {
    //Cacheadadaptiveinstance is used to cache adaptive extension class instances
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
        if (createAdaptiveInstanceError == null) {
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        instance = createAdaptiveExtension();
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        // ...
                    }
                }
            }
    }

    return (T) instance;
}

Getadapteextension() method is used to get the current adaptive extension class instance. First, it will be obtained from the cacheadadaptiveinstance object. If the value is null and createadapteinstanceerror is empty, the createadapteextension method will be called to create the extension class instance. Update cacheadadaptiveinstance after creation.

createAdaptiveExtension()

private T createAdaptiveExtension() {
    try {
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        //Ellipsis anomaly
    }
}

Here are two methods that deserve our attention: injectextension () and getadapteextensionclass (). Injectextension () looks like a method that implements the injection function, while getadapteextensionclass () is used to obtain the specificAdaptive extension class。 Let’s look at the two methods in turn.

injectExtension()

private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            for (Method method : instance.getClass().getMethods()) {
                if (method.getName().startsWith("set")
                        && method.getParameterTypes().length == 1
                        && Modifier.isPublic(method.getModifiers())) {
                    //Skip if disableinject annotation exists
                    if (method.getAnnotation(DisableInject.class) != null) {
                        continue;
                    }
                    //Get the type of the first parameter of method
                    Class<?> pt = method.getParameterTypes()[0];
                    try {
                        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            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;
}

Simply summarize what this method does: traverse the set method of the current instance, take the string from the fourth bit to the end of the set method as the keyword, and try to obtain the corresponding extension class implementation through objectfactory. If there is a corresponding extension class, it is injected into the current instance through reflection. This method is equivalent to a simple dependency injection function. We often say that the IOC in Dubbo is also embodied here.

getAdaptiveExtensionClass()

private Class<?> getAdaptiveExtensionClass() {
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

Next, look at the getadapteextensionclass () method. First, call getextensionclasses() method, return if cacheadadaptiveclass() is not null, and call createadadaptiveextensionclass() method if it is null. Look at the two methods in turn.

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

The content is relatively simple. Let’s look directly at the loadextensionclasses () method.

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);
            //There can only be one default extension instance
            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<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    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;
}

It’s been a long time, and finally it’s about to enter the theme. Why does it enter the theme? Look at the values of these variables

  • DUBBO_INTERNAL_DIRECTORY:META-INF/dubbo/internal/
  • DUBBO_DIRECTORY:META-INF/dubbo/
  • SERVICES_DIRECTORY:META-INF/services/

Familiar formula familiar material.. Yes, we will start to read the files in these three directories and then load our extension points.

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) {
        // ...
    }
}
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) {
                //#Remove the existing notes corresponding to the note location
                final int ci = line.indexOf('#');
                if (ci >= 0) {
                    line = line.substring(0, ci);
                }
                line = line.trim();
                if (line.length() > 0) {
                    try {
                        String name = null;
                        //The contents of the file are saved in the form of key = value, and the key and vlaue are split
                        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) {
                        // ...
                    }
                }
            }
        } finally {
            reader.close();
        }
    } catch (Throwable t) {
        // ...
    }
}
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    //Implementation class used to determine whether a class is a type interface
    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 the current class is marked with @ adaptive annotation, update the cacheadadaptiveclass cache object
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        if (cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        } else if (!cachedAdaptiveClass.equals(clazz)) {
            //Ellipsis anomaly
        }
    } else if (isWrapperClass(clazz)) {
    //This involves another mechanism of Dubbo extension point: packaging, which will be introduced later
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    } else {
        clazz.getConstructor();
        //If name is empty, call the findannotationname() method. If the current class has @ extension annotation, directly return @ extension annotation value;
        //If there is no @ extension annotation, but the class name is similar to XXX type (type represents the class name of type), the return value is lower case XXX
        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) {
            //The @ activate annotation is used to configure the automatic activation condition of extension
            //If the current class contains @ activate, add it to the cache
            Activate activate = clazz.getAnnotation(Activate.class);
            if (activate != null) {
                cachedActivates.put(names[0], activate);
            } else {
                // support com.alibaba.dubbo.common.extension.Activate
                com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
                if (oldActivate != null) {
                    cachedActivates.put(names[0], oldActivate);
                }
            }
            for (String n : names) {
                if (!cachedNames.containsKey(clazz)) {
                    cachedNames.put(clazz, n);
                }
                Class<?> c = extensionClasses.get(n);
                if (c == null) {
                    //Do you remember what the document looks like? (name = callsvalue), which we finally saved in the extensionclasses collection
                    extensionClasses.put(n, clazz);
                } else if (c != clazz) {
                    // ...
                }
            }
        }
    }
}

This piece of code is really quite long.. After combing, I found that what he did was very simple:

  1. Splicing generation file name: dir + type, read the file
  2. Read the content of the file and split it into name and class strings
  3. If the clazz class contains the @ adaptive annotation, add it to the cacheadadaptiveclass cache
    If the clazz class is a wrapper class, add it to wrappers
    If the file is not in the form of key = class, the @ extension annotation will be used to get the name
    If clazz contains @ activate annotation (compatible with com.alibaba.dubbo.common.extension.activate annotation), add it to cachedactivates cache
  4. Finally, take name as key and clazz as vlaue, add it to the extensionclasses collection and return

Get adaptive extension class

getAdaptiveExtensionClass()

private Class<?> getAdaptiveExtensionClass() {
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

OK, we have analyzed the getextensionclasses method and loaded the extension point implementation into the cache. This method is introduced by the getadapteextensionclass () method, which looks like creating an adaptive extension class. Here, we will first determine whether the cacheadadaptiveclass will be empty. When was the cacheadadaptiveclass initialized? Review the previous code:

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    // omission.
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        if (cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        } else if (!cachedAdaptiveClass.equals(clazz)) {
            // omission.
        }
    }
}

In the loadclass () method, if the current clazz contains @ adaptive annotation, the current clazz is saved as a cache adaptive class. For example, it is used in the adaptive extensionfactory class. We will cache the adaptive extensionfactory class as an adaptive class of extensionfactory type.

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory

We continue to analyze the latter part of the method. If cacheadadaptiveclass is null, the createadadaptiveextensionclass() method is called to dynamically generate an adaptive extension class.

private Class<?> createAdaptiveExtensionClass() {
    String code = createAdaptiveExtensionClassCode();
    System.out.println(code);
    ClassLoader classLoader = findClassLoader();
    org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

This code is not intended to be highlighted in this sharing. It can be simply understood that Dubbo helped me generate an adaptive class. I extracted the generated code as follows:

package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory {
    private static final org.apache.dubbo.common.logger.Logger logger = org.apache.dubbo.common.logger.LoggerFactory.getLogger(ExtensionLoader.class);
    private java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger(0);

    public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException {
        if (arg2 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg2;
        String extName = url.getParameter("proxy", "javassist");
        if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = null;
        try {
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        }catch(Exception e){
            if (count.incrementAndGet() == 1) {
                logger.warn("Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.", e);
            }
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension("javassist");
        }
        return extension.getInvoker(arg0, arg1, arg2);
    }
    public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("proxy", "javassist");
        if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = null;
        try {
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        }catch(Exception e){
            if (count.incrementAndGet() == 1) {
                logger.warn("Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.", e);
            }
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension("javassist");
        }
        return extension.getProxy(arg0, arg1);
    }
    public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("proxy", "javassist");
        if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = null;
        try {
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        }catch(Exception e){
            if (count.incrementAndGet() == 1) {
                logger.warn("Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.", e);
            }
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension("javassist");
        }
        return extension.getProxy(arg0);
    }
}
extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);

This code is actually the essence of adaptive adaptation class. Let’s see how extname comes from?

String extName = url.getParameter("proxy", "javassist");

Extname is obtained from the URL. In fact, the URL is a very important context transport carrier for Dubbo. You will gradually feel it in the following series of articles.

public T getExtension(String name) {
    if (name == null || name.length() == 0) {
        throw new IllegalArgumentException("Extension name == null");
    }
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    //Read extension implementation class from cache
    Holder<Object> holder = cachedInstances.get(name);
    if (holder == null) {
        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;
}

The above logic is relatively simple. I won’t go over it here. Let’s look at the createextension () method directly.

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()) {
            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);
    }
}

The getextensionclasses() method has been analyzed previously, but it should be noted that:Getextensionclasses returns only the classes loaded with class. Forname(), which at best executes the static code segments instead of getting the real instance. The real instance object still needs to call the class. Newinstance() method to get it.
After understanding these, we will continue to see that we try to get the class object loaded by the system through getextensionclasses(), and then get it from the extended instance cache through the class object. If the extension instance is null, callnewInstance()Method to initialize the instance and put it into the extension “instances cache. Then call the injectextension () method for dependency injection. The last paragraph deals with the use of wrapper classes, which will be covered in the next chapter.

Wrapper for extension class

In the createextension() method, there is a code as follows:

private T createExtension(String name) {
    //Omitted···
    Set<Class<?>> wrapperClasses = cachedWrapperClasses;
    if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
        for (Class<?> wrapperClass : wrapperClasses) {
            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
        }
    }
    return instance;
    //Omitted···
}

Remember where wrapperclasses were initialized? We’ve covered this in the loadclass () method earlier. To recap:

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    //Omitted···
    if (isWrapperClass(clazz)) {
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    }
    //Omitted···
}

private boolean isWrapperClass(Class<?> clazz) {
    try {
        clazz.getConstructor(type);
        return true;
    } catch (NoSuchMethodException e) {
        return false;
    }
}

Before looking at this method, let’s first understand the definition of wrapper class in Dubbo.
for instance:

class A {
    private A a;
    public A(A a){
        this.a = a;
    }
}

We can see that class A has a construction method with a as parameter, which we call replication construction method. The class with such construction method is called wrapper class in Dubbo.
Continue to see the iswrapperclass() method, which is relatively simple. Try to get the construction method with type as the parameter in clazz. If it can be obtained, it is considered that clazz is the wrapper class of the current type class. Combined with the above code, we will find that when we load the extension point, we cache the wrapper class corresponding to type.

private T createExtension(String name) {
    //Omitted···
    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()) {
        for (Class<?> wrapperClass : wrapperClasses) {
            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
        }
    }
    return instance;
    //Omitted···
}

In order to better understand this code, let’s assume that the current type value is protocol.class. We can find the protocol interface’s wrapper classes, protocolfilterwrapper and protocollistenerwrapper, in the org.apache.dubbo.rpc.protocol file. They will be added to the cachedwrapperclasses collection in turn. Traverse the cachedwrapperclasses collection in turn. For example, if the protocolfilterwrapper class is retrieved for the first time, the instance will be wrapped by calling the replication construction method of protocolfilterwrapper. After the protocolfilterwrapper object instance is created, injectextension() is called for dependency injection. At this time, instance is already an instance of protocolfilterwrapper. Continue the loop, and the protocolfilterwrapper class will be wrapped in the protocollistenerwrapper class. So we finally return a protocollistenerwrapper instance. At the end of the call, the call to the original instance will still be invoked through a layer of layer.
The packaging class here is a bit similar to AOP idea. We can add some custom operations such as log printing and monitoring before calling the extension implementation through layer by layer packaging.

IOC mechanism in Dubbo

We have discussed the implementation of an IOC like function in Dubbo by using reflection mechanism. In this chapter, we will review the injectextension () method and take a closer look at the implementation of IOC functions in Dubbo.

createExtension()
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));

private T injectExtension(T instance) {
    // ···
    Class<?> pt = method.getParameterTypes()[0];
    try {
        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
        Object object = objectFactory.getExtension(pt, property);
        if (object != null) {
            method.invoke(instance, object);
        }
    }
    // ···
}

public class StubProxyFactoryWrapper implements ProxyFactory {
    // ...
    private Protocol protocol;
    public void setProtocol(Protocol protocol) {
        this.protocol = protocol;
    }
    //...
}

We’ve talked about the wrapper class in the previous chapter. Here’s an example. For example, if our current wrapperclass class is stubbproxyfactorywrapper, the code execution logic is roughly as follows:

  1. Create an instance of stubbproxyfactorywrapper;
  2. Obtain the instance created in process 1 as the parameter of injectextension(), and execute;
  3. The injectextension() method iterates to the setprotocol() method of stubbproxyfactorywrapper (at this time, Pt = protocol. Class, property = protocol), and executes the objectfactory.getextension (PT, property) method. Objectfactory is initialized in the construction method of extensionloader, and the adaptive extension class is obtained here as adaptive extensionfactory.
  4. Execute adapteextensionfactory. Getextension(). The adapteextensionfactory class has a collection variable, factories. Factories are initialized in the construction method of adaptive extensionfactory, which contains two factory classes: spiextensionfactory and springextensionfactory. Executing the getextension() method of the adaptive extensionfactory class calls the getextension() method of the spiextensionfactory and springextensionfactory classes in turn.
  5. Execute the getextension() method of spiextensionfactory. As mentioned above, type = protocol. Class, property = protocol. From the following code, we can find that protocol is an interface class, and @ SPI annotation is marked. At this time, extensionloader object of protocol type will be obtained, and finally, getadaptiveextension() method of loader will be called. The final adaptive class obtained is protocol $adaptive dynamic class.
  6. Objectfactory. Getextension (PT, property); the final class is protocol $adaptive, which is injected into the stub proxyfactorywrapper instance by reflection mechanism.
@SPI("dubbo")
public interface Protocol {
}
public class SpiExtensionFactory implements ExtensionFactory {
    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }
}

END

In the end, let’s review the first part about Dubbo SPI’s improvement based on Java SPI:

DubboExtension pointThe load is enhanced from the SPI (service provider interface) extension point discovery mechanism of the JDK standard.
The spi of the JDK standard instantiates all the implementations of extension points at one time. If there is an extension implementation initialization, it is time-consuming, but if it is not used, it will also load, which will waste resources.
If the extension point fails to load, the name of the extension point cannot be obtained. For example: the JDK standard scriptengine gets the name of the script type through getname(), but if rubyscriptengine fails to load the rubyscriptengine class because the jruby.jar it depends on does not exist, the failure reason is eaten, which does not correspond to ruby. When users execute Ruby scripts, they will report that they do not support Ruby, rather than the real failure reason.Support for IOC and AOP is added. One extension point can be directly set to inject other extension points

The summary is as follows:

  1. Dubbo SPI will save the extension class in the cache in the form of key value when loading the extension point, but the extension class at this time is only the class loaded by calling class. Forname(), which is not instantiated. The extension class is instantiated when the getextension () method is called.
  2. Dubbo implements dependency injection through factory mode and reflection mechanism.
  3. In Dubbo, AOP mechanism is implemented through wrapper class, which is convenient for us to add monitoring and print logs.

The original articles on this blog are not allowed to be used for commercial purposes and traditional media without my permission. Please indicate the source of network media reprint, otherwise it is a tort. https://juejin.im/post/5c0cd7…

Recommended Today

Android access scanning function (cameraX + zxing)

How to integrate: 1. In the root directory of build Add jitpack dependency to gradle: allprojects { repositories { maven { url “https://jitpack.io” } } } 2. In project build Add yxing dependency to gradle: implementation ‘com.github.amggg:YXing:V1.0.6’ Function: 1. Scan QR code and barcode.2. Generate QR code with logo QR code and barcode.3. Identify the […]