Detailed explanation of Dubbo SPI mechanism

From the principle of java spi in the previous article, we can see that java spi mechanism has the following disadvantages:

  • You can only traverse all implementations and instantiate them all.
  • The configuration file simply lists all the extension implementations without naming them. It makes it difficult to accurately reference them in the program.
  • If the extension depends on other extensions, it cannot be automatically injected and assembled.
  • Extension is difficult to integrate with other frameworks. For example, the extension relies on a Spring bean, which is not supported by native Java SPI.

To understand the basic idea and application of JAVA SPI, please refer to:

Detailed explanation of JAVA SPI mechanism

We all know that a qualified open source framework is flexible to support extension, and Dubbo is no exception. Dubbo adopts a simple extension method based on SPI mechanism. But I have realized a set of others and improved the following points

  • The SPI of the JDK standard instantiates all the implementations of extension points at one time. If there is an extension implementation initialization, it will take a long time, 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 depends on the jruby.jar No, it causes the RubyScriptEngine class to fail to load. This failure reason is eaten and does not correspond to ruby. When the user executes the ruby script, it will report that it does 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.

Basically speaking, you may have a general understanding of SPI here, but to really understand Dubbo's SPI, you need to look at the source code carefully.

Before understanding Dubbo's SPI, several core concepts should be clarified

(1) extension point: an interface.

Dubbo's flexible framework does not force all users to use certain architectures provided by Dubbo. For example, in the Registry, Dubbo provides zk and redis, but if we prefer other registries, we can replace those provided by Dubbo. For this replaceable technology implementation point, we call it extension point. There are many similar extension points, such as Protocol, Filter, Loadbalance and so on.

(2) extension: implementation of extension (Interface).

When Dubbo loads the extension class of an interface, if there is a copy class constructor in an implementation, the interface implementation is the Wrapper class of the interface. At this time, Dubbo will cover the Wrapper on the upper layer of the real implementation class. That is to say, the actual extension class returned from ExtensionLoader at this time is the interface implementation class wrapped by Wrapper

(3) Extension adaptive instance: in fact, it is an Extension agent

It implements the extension point interface. When calling the interface method of the extension point, it will decide which extension to use according to the actual parameters. dubbo will automatically decide which implementation to choose based on the parameters in the interface.

(4) @ SPI: this annotation acts on the interface of the extension point, indicating that the interface is an extension point.

(5) @ Adaptive: the @ Adaptive annotation is used to extend the methods of the interface.

Indicates that this method is an Adaptive method. When Dubbo generates Adaptive instances for extension points, if the method has @ Adaptive annotation, the corresponding code will be generated for the method.

This adaptive extension point is difficult to understand, so let's take an example to explain: in RegistryProtocol, there is an attribute cluster, in which Protocol and cluster are extension points provided by Dubbo, so which cluster implementation class should we use when we use cluster in operation? FailbackCluster or FailoverCluster? When Dubbo loads an extension point, if its member variable is also an extension point and there are related set methods, it will set the extension point as an adaptive extension point at this time. Adaptive extension point will obtain relevant parameters from the URL when it is actually used to call the real extension point implementation class. The specific implementation will be explained in detail in the following source code. As for the understanding of adaptive, I recommend Dubbo developer's guide, which has a clear introduction to adaptive.

(6) the name of Activate official website is self activation. In fact, the more appropriate name is conditional activation.

The function of Activate is to provide a condition for selective activation. We can determine which functions are activated through related configuration.

(7) ExtensionLoader is a tool class for finding service implementation of dubbo's SPI mechanism, similar to Java's ServiceLoader, which can be used as an analogy. dubbo specifies that the extension point configuration file is placed in the directory of / META-INF/dubbo, / META-INF/dubbo/internal, / META-INF/services under classpath. The name of the configuration file is the fully qualified name of the interface. The content of the configuration file is the configuration name = the fully qualified name of the extension implementation class.

Having said so much, let's take a look at the code first

Interface

package org.alexsotob.dubbo.SPI;

import com.alibaba.dubbo.common.extension.SPI;

@SPI
public interface Animal {

    String getName();

}

Implementation class Dog

package org.alexsotob.dubbo.SPI.impl;

import org.alexsotob.dubbo.SPI.Animal;

public class Dog implements Animal {
    public Dog() {
        System.out.println("dog init !!!");
    }

    @Override
    public String getName() {
        return "I am dog";
    }
}

Implementation class cat

package org.alexsotob.dubbo.SPI.impl;

import org.alexsotob.dubbo.SPI.Animal;

public class Cat implements Animal {
    public Cat() {
        System.out.println("cat init !!!");
    }

    @Override
    public String getName() {
        return "I am cat";
    }
}

An important step, ExtensionLoader, is a tool class for dubbo's SPI mechanism to find service implementation, similar to Java's ServiceLoader, which can be used as an analogy. dubbo specifies that the extension point configuration file is placed in the directory of / META-INF/dubbo, / META-INF/dubbo/internal, / META-INF/services under classpath. The name of the configuration file is the fully qualified name of the interface. The content of the configuration file is the configuration name = the fully qualified name of the extension implementation class.

The format of configuration is different from that of java Native, similar to the format of property file:

cat=org.alexsotob.dubbo.SPI.impl.Cat
dog=org.alexsotob.dubbo.SPI.impl.Dog

Write test class entry as ExtensionLoader

package org.alexsotob.dubbo.SPI;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class DubboSPITest {

    public static void main(String[] args) {
        ExtensionLoader<Animal> carExtensionLoader = ExtensionLoader.getExtensionLoader(Animal.class);        //Get the implementation class object on demand
        Animal animal = carExtensionLoader.getExtension("cat");
        System.out.println(animal.getName());
    }

}

This is just a simple local Demo, which is used to introduce the idea of dubbo SPI. dubbo also implements other functions compatible with Spring IOC

The basic process is as follows. Remember it. Only after that can the source code analysis ideas be clear:

The source code analysis is as follows:

In the dubbo SPI example method, we first obtain the ExtensionLoader instance of an interface through the getExtensionLoader method of the ExtensionLoader, and then obtain the extension class object through the getExtension method of the ExtensionLoader. The source code is as follows. The first is the getExtensionLoader method:

 /**
     * Extension class loader cache, that is, extension point ExtendsLoader instance cache; key = extension interface value = extension class loader
     */
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();

    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {        
        //Verify that the type class passed in is empty
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }        
        //Verify that the type class passed in is an interface
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
        }        
        //Verify that the type class passed in has @ SPI annotation
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }        
        //Query from the ExtensionLoader cache whether there is already an ExtensionLoader instance of the corresponding type
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {            //There is no new instance of ExtensionLoader and it is stored in the local cache
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

getExtensionLoader will verify the incoming interface, including whether there is @ SPI annotation verification, which is also the reason for adding @ SPI to the interface. Then from extension_ In the loaders cache, get the ExtensionLoader of the interface type. If not, create an ExtensionLoader of the interface type and put it into the cache, and return the ExtensionLoader.

Note that the construction method of the extensionloader object created here is as follows: ExtensionLoader.getExtensionLoader Get the extension class of the ExtensionFactory interface, and then get the target extension class from the extension class through getadaptive extension. It sets the objectFactory constant corresponding to the interface to Adaptive ExtensionFactory. Because @ Adaptive annotation is added to the class of Adaptive ExtensionFactory, the reason why it is Adaptive ExtensionFactory will be explained in later articles, and objectFactory will also be used later.

private ExtensionLoader(Class<?> type) {
        this.type = type;        
        //type is usually not an ExtensionFactory class,
        // Then objectFactory is the default extension class of ExtensionFactory interface, AdaptiveExtensionFactory
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

When passed ExtensionLoader.getExtensionLoader After getting the Loader loader of the interface, get the class object to be extended through the getExtension method.

 /**
     * Extension point instance cache key = extension point name, value = Holder instance of extension instance
     */
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();

    /**
     * Get interface extension class instance
     * 1.Check for presence in cache
     * 2.Create and return extension class instance
     *
     * @param name The key of the extension class in the configuration file to be obtained
     * @return
     */
    public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {            
            // Get the default extension implementation class, that is, the default implementation class on the @ SPI annotation, such as @ SPI("benz")
            return getDefaultExtension();
        }        
        // Holder, as the name implies, is used to hold the target object, take it from the cache, and create if it is not
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();        // duplication check
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {                   
                    // Create an extension instance
                    instance = createExtension(name);                    
                    // Set instance to holder
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

    /**
     * Get or create a Holder object
     */
    private Holder<Object> getOrCreateHolder(String name) {        
        // First, get the Holder object from the extension instance cache through the extension
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {            
            //If not, a new empty Holder instance will be cached
            cachedInstances.putIfAbsent(name, new Holder<>());
            holder = cachedInstances.get(name);
        }
        return holder;
    }

The logic of the above code is relatively simple. First, check the cache. If the cache misses, create an extension object. Dubbo contains a large number of extended point caches. This is a typical way of using space for time. It is also one of the reasons why Dubbo has strong performance, including

  1. When Dubbo SPI obtains the extension point, it will first read from the cache. If the cache does not exist, it will load the configuration file, cache the Class into memory according to the configuration, and it will not initialize directly.
  2. Extend the point instance cache. Dubbo will not only cache Class, but also the instance of Class. Each instance will be fetched from the cache first, otherwise it will be loaded from the configuration, instantiated and cached into memory.

Let's take a look at the process of creating extension objects

/**
     * The extension instance is stored in memory and cached; key = extension class; value = extension class instance
     */
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();

    /**
     * To create an extension class instance, follow these steps
     * 1. Get all extension classes through getExtensionClasses and get the map map map of extension classes from the configuration file load
     * 2. Creating extended objects by reflection
     * 3. Injecting dependency (IOC) into extended objects
     * 4. Wrap the extension object in the corresponding Wrapper object (AOP)
     *
     * @param name The key of the extension class in the configuration file to be obtained
     * @return Examples of extension classes
     */
    private T createExtension(String name) {       
        // Load all extension classes from the configuration file to get the map from "configuration item name" to "configuration class",
        // Then take the corresponding extension class from the map according to the extension item name
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {            
            //Get the corresponding instance object from the extension point cache
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {                
                // //If there is no such extension point in the cache, the instance is created through reflection and stored in the cache
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());                
                //Then get the corresponding instance from the cache
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }            
            // Inject dependency into the instance, and automatically inject the corresponding property instance through setter method
            injectExtension(instance);            
            //Take all packaging classes out of the cache to form a packaging chain
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {               
                // Create Wrapper instance circularly to form Wrapper chain
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }            
            //Initialize instance and return
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException(".....");
        }
    }

The steps to create extension class objects are as follows:

  1. Load all extension classes from the configuration file through getExtensionClasses, and then get the target extension class by name
  2. Creating extended objects by reflection
  3. Injecting dependency into extended objects
  4. Wrap the extension object in the corresponding Wrapper object

The third and fourth steps are the concrete implementation of Dubbo IOC and AOP. First, we focus on the logic of getExtensionClasses method.

Load all extension classes from the configuration file

  • Before obtaining the extension class by name, the mapping map between the extension item name and the extension class needs to be resolved according to the configuration file, and then the corresponding extension class can be taken out from the map according to the extension item name. The source code of getExtensionClasses method is as follows

/**
     * Extension point class cache key = extension, value = corresponding class object
     */
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

    /**
     * Parsing the mapping table map of interface extension name and extension class in configuration file
     *
     * @return
     */
    private Map<String, Class<?>> getExtensionClasses() {
        // Get the loaded extension point class from the cache
        Map<String, Class<?>> classes = cachedClasses.get();
        //duplication check
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // Load extension class
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

Check the cache first. If the cache fails to hit, load the extension class through loadExtensionClasses. The cache avoids the time-consuming of reading the configuration file multiple times. The following analyzes the logic of the loadExtensionClasses method to load the configuration file

/**
     * Path of three default dubbo SPI scans
     */
    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 Map<String, Class<?>> loadExtensionClasses() {      
        //Get and cache the default implementation class on the @ SPI annotation of the interface ,@SPI Value in ("value")
        cacheDefaultExtensionName();
        Map<String, Class<?>> extensionClasses = new HashMap<>();      
        // Load the configuration file under the specified folder. The constants contain three folders: META-INF/dubbo/internal /, META-INF/dubbo /, META-INF/services /
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName(), true);      
        //Compatible with historical versions
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"), true);
        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;
    }

The loadExtensionClasses method does two things in all. First, the method calls cacheDefaultExtensionName to parse the SPI annotation, and gets and caches the default extension class on the @ SPI annotation of the interface in cachedDefaultName. Then call the loadDirectory method to load the specified folder profile.

SPI annotation parsing process is relatively simple, the source code is as follows. Only one default extension class is allowed.

 private void cacheDefaultExtensionName() {        
        // Get the SPI annotation. The type variable here is passed in when the getExtensionLoader method is called, representing the interface class
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation == null) {
            return;
        }
        String value = defaultAnnotation.value();
        if ((value = value.trim()).length() > 0) {
            String[] names = NAME_SEPARATOR.split(value);            
            // Check whether the SPI annotation content is legal (at most one default implementation class) and throw an exception if it is illegal
            if (names.length > 1) {
                throw new IllegalStateException("...");
            }            
            // Set default extension class name
            if (names.length == 1) {
                cachedDefaultName = names[0];
            }
        }
    }

From the source code, we can see that there are three paths for the loadExtensionClasses method to load configuration files: META-INF/dubbo/internal /, META-INF/dubbo /, META-INF/services /. The method source code is as follows:

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        loadDirectory(extensionClasses, dir, type, false);
    }

    /**
     * Load profile content
     *
     * @param extensionClasses                Extended class map
     * @param dir                             Folder path
     * @param type                            Interface name
     * @param extensionLoaderClassLoaderFirst Whether to load ClassLoader of ExtensionLoader first
     */
    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst) {       
        // fileName = folder path + type fully qualified name
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls = null;
            //Get the classloader of the current thread
            ClassLoader classLoader = findClassLoader();
            // try to load from ExtensionLoader's ClassLoader first
            if (extensionLoaderClassLoaderFirst) {
                //Get load ExtensionLoader.class Class loader of this class
                ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader(); 
                //If the extensionLoaderClassLoaderFirst=true and the two class loaders are different, the extensionLoaderClassLoader is preferred
                if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
                    urls = extensionLoaderClassLoader.getResources(fileName);
                }
            }
            // Load all files with the same name according to the file name
            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();
                    // Parse and load the implementation classes configured in the configuration file to the extensionClasses
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error(""). ", t);
        }
    }

First, find the configuration file under the folder. The file name should be the fully qualified name of the interface. The class loader is used to get the file resource link, and then the implementation class configured in the configuration file is parsed and added to the extensionClasses. Let's continue to see how loadResource loads resources.

 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;                
                // Read configuration content by line
                while ((line = reader.readLine()) != null) {
                    final int ci = line.indexOf('#');
                    if (ci >= 0) {                        
                        // Intercept the string before ා, and the content after ා, which is a comment, needs to be ignored
                        line = line.substring(0, ci);
                    }
                    line = line.trim();
                    if (line.length() > 0) {
                        try {
                            String name = null;
                            int i = line.indexOf('=');
                            if (i > 0) {                                
                                // Cut off the key and value by the equal sign =
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {                                
                                // Load the class through reflection and cache the class through the loadClass method
                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                            }
                        } catch (Throwable t) {
                            .....
                        }
                    }
                }
            }
        } catch (Throwable t) {
            logger.error(....);
        }
    }

The loadResource method is used to read and parse the configuration file. The configuration file is read by line. Each line is bounded by the equal sign = to intercept the key and value. The class is loaded through reflection. Finally, the class of the class is loaded into the map through the loadClass method to load the extension point, and the loaded class is classified and cached. The loadClass method is implemented as follows

/**
     * Load the class of the extension point implementation class into the map, and cache the loaded class by classification
     * For example, cacheadadaptiveclass, cachedWrapperClasses, cachedNames, etc
     *
     * @param extensionClasses Container for loading profile classes
     * @param resourceURL      Profile resource URL
     * @param clazz            Class of extension point implementation class
     * @param name             Name of extension point implementation class, key in the configuration file line
     * @throws NoSuchMethodException
     */
    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        //Determine whether the configured implementation class implements the type interface
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("...");
        }
        //Cache according to the type of implementation class in the configuration
        // Check whether there is an Adaptive annotation on the target class, indicating that this class is an Adaptive implementation class, and cache it to cacheadadaptiveclass
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz);
            // Check whether clazz is a Wrapper type, and judge whether there is a construction method whose parameter is the interface class, and cache it to cachedWrapperClasses
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            // Check whether clazz has a default constructor, if not, throw an exception
            clazz.getConstructor();
            // If the name of the key in the configuration file is empty, try to get the name from the Extension annotation, or use the lowercase class name as the name.
            // It's abandoned. We're not talking about it
            if (StringUtils.isEmpty(name)) {
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("...");
                }
            }
            //Use comma to divide name into string array
            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                //If the implementation class configured by extension point uses @ Activate annotation, the corresponding annotation information will be cached
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    //Cache extension point implementation class and extension point name correspondence
                    cacheName(clazz, n);
                    //Finally, save the class to extensionClasses
                    saveInExtensionClass(extensionClasses, clazz, n);
                }
            }
        }
    }

The loadClass method implements the classified caching function of extension points, such as packaging class, Adaptive extension point implementation class, and common extension point implementation class. Note that the Adaptive extension point implementation class @ Adaptive annotation has the following source code

/**
 * For example, given <code>String[] {"key1", "key2"}</code>:
 * <ol>
 * <li>find parameter 'key1' in URL, use its value as the extension's name</li>
 * <li>try 'key2' for extension's name if 'key1' is not found (or its value is empty) in URL</li>
 * <li>use default extension if 'key2' doesn't exist either</li>
 * <li>otherwise, throw {@link IllegalStateException}</li>
 *
 * @return
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    String[] value() default {};
}

The function of the annotation is to determine which adaptive extension class is injected. The target extension class is determined by the parameters in the URL. The parameter key in the URL is given by the value of the annotation. The value of the key is the name of the target extension class.

  • If there are multiple values in the annotation, search for the corresponding key in the URL according to the subscript from small to large. Once found, use the value of the key as the target extension class name.
  • If none of these values have a corresponding key in the url, use the default value on the spi.

@In most cases, the adaptive annotation is applied to methods. When the adaptive annotation is applied to a class, Dubbo will not generate a proxy class for the class. When annotated on a method (interface method), Dubbo generates a proxy class for that method. Adaptive annotation in the interface method indicates that the extended loading logic needs to be automatically generated by the framework. Annotation on the class, the extended loading logic is manually encoded.

The loadClass scan above is for classes. In Dubbo, only two classes are annotated by @ Adaptive, namely, Adaptive compiler and Adaptive extensionfactory.
Setting cacheAdaptiveClass with the loadClass method will cause the cacheAdaptiveClass of the interface not to be empty. Later, this extension class will be used by default with the highest priority.

Back to the main line, when the loadClass method is executed, all the extension classes in the configuration file have been loaded into the map, so far, the process of loading the cache class is analyzed.

Dubbo IOC

After the execution process of getExtensionClasses() method is completed, the extension Class can be obtained by taking the corresponding extension Class from the map according to the extension item name, creating an instance through reflection, and injecting dependency into the instance through the injectExtension(instance); method to summarize the later

DUBBO AOP

After the implementation of the inject extension (t instance) method, the wrapper is executed in the createExtension(String name). Similar to AOP in spring, dubbo uses the decorator mode.

 Set<Class<?>> wrapperClasses = cachedWrapperClasses;        
    if(CollectionUtils.isNotEmpty(wrapperClasses)){            // Create Wrapper instance circularly to form Wrapper chain
        for (Class<?> wrapperClass : wrapperClasses) {
            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
        }
    }

The cachedWrapperClasses here is known through the previous analysis, that is, when parsing the configuration file, judge whether it is an extension class of Wrapper type, and + + judge whether it is based on the interface class + + in the construction method, and cache it to cachedWrapperClasses.

Execution wrapperClass.getConstructor(type).newInstance(instance) will get the construction method of the Wrapper class. The parameter of the method is the interface type, and generate a Wrapper object containing the instance of the extension class through reflection, and then inject the dependency of the Wrapper object through injectExtension. In this way, the Wrapper chain is generated. Note here that the Wrapper class behind the content in the configuration file will be wrapped in a relatively outer layer. The following is an example of DUBBO AOP. We continue to use the above interface and implementation class, and add an implementation class. The code is as follows

package org.alexsotob.dubbo.SPI.wrapper;

import org.alexsotob.dubbo.SPI.Animal;

public class AnimalWrapper implements Animal {
    private Animal animal;
    public AnimalWrapper(Animal animal) {
        this.animal = animal;
    }

    @Override
    public String getName() {
        System.out.println("data verification ");
        String result = animal.getName();
        System.out.println("Logging");
        return result;
    }
}

Content modification of META-INF/dubbo file:

cat=org.alexsotob.dubbo.SPI.impl.Cat
dog=org.alexsotob.dubbo.SPI.impl.Dog
org.alexsotob.dubbo.SPI.wrapper.AnimalWrapper #Packaging

Using the previous test class test, the results are as follows

As we expected, it implements the aspect function of Wrapper class

If there is anything wrong, I hope it can be corrected by you

This article refers to the blog

http://blog.itpub.net/31559758/viewspace-2678246/

 

 

Tags: Dubbo Java Spring Ruby

Posted on Wed, 17 Jun 2020 01:21:18 -0400 by Rineru