Explanation of SPI Extension Mechanism in Dubbo

Keywords: Dubbo Java Attribute Ruby

Previously, we learned about Java's SPI extension mechanism. We also have a general understanding of the principles, advantages and disadvantages of Java extension mechanism. Here, we will go further into Dubbo's extension point loading mechanism.

Functions of Dubbo Extension Point Loading

Dubbo's extension point loading mechanism is similar to Java's SPI. We know that when Java's SPI is used, it can only be found and instantiated through traversal. It is possible to instantiate all implementations at once. This will result in some unused extension implementations being instantiated, which will result in certain capital. Source waste. For Dubbo improvements, refer to the documentation:

  • The JDK standard SPI instantiates all implementations of extension points at one time. Initialization of extension implementations is time-consuming, but it wastes resources if they are not loaded.
  • If the extension point fails to load, the name of the extension point is not available. For example: script Engine of JDK standard, get the name of script type through getName(); but if RubyScript Engine fails to load RubyScript Engine class due to the absence of jruby.jar on which it relies, the failure reason is eaten up, which is not corresponding to ruby. When Ruby script is executed, users will report that they do not support ruby. It's not the real cause of failure.
  • Adding support for extension points IoC and AOP, one extension point can be directly set to inject other extension points.

As for the first point, you can see by comparing it with the SPI of Java; the second point has not been tested, and the reason is not clear; and the third point is briefly introduced below for IOC and AOP support.

Extended Point Automatic Assembly (IOC)

When an extension point is loaded, other extension points on which the extension point depends are automatically injected. If the description is not clear, see the following example:

Interface A, Implementing Classes A1, A2
 Interface B, Implementing Classes B1, B2

The implementation class A1 contains setB() method. When the implementation of A is loaded through the extension mechanism, an implementation class of B is automatically injected. However, at this time, instead of injecting B1 or B2, an implementation class of B is injected: B$Adpative, which is dynamically generated and can be based on different parameters. Automatically select B1 or B2 for invocation.

Extended Point Adaptation

As we said above, in automatic assembly, it is not to inject a real implementation, but to inject an adaptive extension point implementation. In fact, it is the dynamically generated code, that is, the manual assembly code, which will add the selection function for the specific implementation according to the configuration information on the SPI. The generated code is similar to the following. The code is streamlined and the package is removed.

import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative implements Protocol {
  public Invoker refer(Class arg0, URL arg1) throws Class {
    if (arg1 == null) throw new IllegalArgumentException("url == null");

    URL url = arg1;
    String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );

    if(extName == null) throw new IllegalStateException("Fail to get extension(Protocol) name from url(" + url.toString() + ") use keys([protocol])");

    Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);

    return extension.refer(arg0, arg1);
  }

  public Exporter export(Invoker arg0) throws Invoker {
    if (arg0 == null) throw new IllegalArgumentException("Invoker argument == null");

    if (arg0.getUrl() == null) throw new IllegalArgumentException("Invoker argument getUrl() == null");URL url = arg0.getUrl();
    //Here, we will get the specific implementation class name based on the information in the url.
    String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );

    if(extName == null) throw new IllegalStateException("Fail to get extension(Protocol) name from url(" + url.toString() + ") use keys([protocol])");
    //Depending on the implementation class name above, the implementation class is loaded at runtime through Dubbo's extension mechanism.
    Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);

    return extension.export(arg0);
  }

  public void destroy() {
    throw new UnsupportedOperationException("method public abstract void Protocol.destroy() of interface Protocol is not adaptive method!");
  }

  public int getDefaultPort() {
    throw new UnsupportedOperationException("method public abstract int Protocol.getDefaultPort() of interface Protocol is not adaptive method!");
  }
}

It is easy to imagine that when we load the extension point implementation, we do not call the specific logic of the implementation. Then we inject an extension point, and we do not know what the implementation of this extension point is, so we need to inject an adaptive implementation. When running, the real implementation is invoked according to the adaptive implementation.

Extension Point Automatic Packaging (AOP)

Let's look at the following example, if interface A has another implementer: AWrapper1:

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

}

AWrapper1 is equivalent to A's packaging class, similar to AOP's function, and AWrapper1 adds A's function. When we get the implementation class of interface A, we get the wrapped class.

Implementation of Dubbo Extension Point Loading

The configuration file is similar to the SPI configuration file of Java. The configuration file of Dubbo is placed in the META-INF/dubbo/directory. The configuration file name is the fully qualified name of the interface. The content of the configuration file is the fully qualified name of the extended implementation class. The function of loading the implementation class is general. Implemented through Extension Loader, similar to the role of Service Loader in Java.

In addition, extension points are loaded using a single instance, which requires thread security.

Some Definitions of Dubbo Extension Point Loading

  • @ SPI annotations, the interface marked by this annotation, represent an extensible interface.
  • @ Adaptive annotation has two ways of annotation: one is annotation on class, the other is annotation on method.

    • Annotations are on classes, and annotations are on implementation classes. At present, only the Adaptive Compiler and the Addressive Extension Factory classes annotate this annotation. These are special classes. Extension Loader relies on them to work, so it has to use this method.
    • Annotations in methods, annotations in interface methods, except for the above two classes, all are annotations in methods. Extension Loader dynamically generates adapter code based on interface definition and instantiates the generated dynamic class. The method annotated by Adaptive generates concrete method implementations. Unsupported OperationException is thrown in the implementation of method generation without annotation. In the generated dynamic class, the annotated method decides which extension to actually call based on the parameter information in the url.

    For example, this code:

    private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

    When the above code is executed, we don't really know what Protocols we want to use. They may be specific implementations of Dubbo Protocol or other specific implementations. So what is refprotocols at this time? refprotocol is actually a class automatically generated when the getAdaptiveExtension() method is called. The code is as follows:

    import com.alibaba.dubbo.common.extension.ExtensionLoader;
    public class Protocol$Adpative implements Protocol {
      public Invoker refer(Class arg0, URL arg1) throws Class {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
    
        URL url = arg1;
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
    
        if(extName == null) throw new IllegalStateException("Fail to get extension(Protocol) name from url(" + url.toString() + ") use keys([protocol])");
    
        Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
    
        return extension.refer(arg0, arg1);
      }
    
      public Exporter export(Invoker arg0) throws Invoker {
        if (arg0 == null) throw new IllegalArgumentException("Invoker argument == null");
    
        if (arg0.getUrl() == null) throw new IllegalArgumentException("Invoker argument getUrl() == null");URL url = arg0.getUrl();
    
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
    
        if(extName == null) throw new IllegalStateException("Fail to get extension(Protocol) name from url(" + url.toString() + ") use keys([protocol])");
    
        Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
    
        return extension.export(arg0);
      }
    
      public void destroy() {
        throw new UnsupportedOperationException("method public abstract void Protocol.destroy() of interface Protocol is not adaptive method!");
      }
    
      public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int Protocol.getDefaultPort() of interface Protocol is not adaptive method!");
      }
    }

    You can see that the methods annotated by @Adaptive generate concrete implementations, and the implementation logic is the same. Unannotated methods directly throw exceptions that do not support operations.

    When we use refprotocol to call a method, we actually call the method in the generated class Protocol$Adpative. The method in this paper finds the specific implementation class according to the parameter configuration in the url, and finds the way to implement the class or through the extension mechanism of dubbo. For example, there may be protocol=dubbo in the url, so we can use this Dubbo to determine that the class we are looking for is DubboProtocol. You can see getExtension(extName) in the generated code, where you look for implementation classes based on specific names.

  • @ Activate annotations, which require annotations on classes or methods, and specify the conditions under which they are activated, as well as the sort information in all the activated implementation classes.

  • Extension Loader is a tool class for dubbo's SPI mechanism to find service implementations, similar to Java's Service Loader. The Dubbo protocol extension point configuration file is placed under the classpath directories of / META-INF/dubbo, / META-INF/dubbo/internal, / META-INF/services. The configuration file name is the fully qualified name of the interface and the content of the configuration file is the fully qualified name of the configuration name = the fully qualified name of the extended implementation class.

Source Code Parsing for Dubbo Extension Point Loading

Emphasis is placed on the Extension Loader class. Dubbo extension points are loaded using a single instance and cached in Extension Loader. Each Extension Loader instance is only responsible for loading the implementation of a specific SPI extension. To obtain the implementation of an extension, the first step is to obtain the corresponding Extension Loader instance of the extension.

Take Protocol as an example to analyze the loading of extension points:

//In this way, the Extension Loader instance is obtained first, and then the adaptive Proocol extension point is loaded.
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
//Use
protocol.refer(Class<T> type, URL url));

As you can see, there are about three steps to load using extension points:

  1. Get the Extension Loader instance.
  2. Acquisition of adaptive implementation.
  3. Use the acquired implementation.

Next we will take these three steps as the dividing line to go deep into source code parsing.

Get an Extension Loader instance

In the first step, getExtension Loader (Protocol. class), according to the interface protocol to be loaded, creates an instance of Extension Loader. The loaded instance will be cached. Next time the Extension Loader of Protocols is loaded, it will use the cached one. No new instance will be created.

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    //Extension point type cannot be empty
    if (type == null)
        throw new IllegalArgumentException();
    //Extension point types can only be interface types
    if(!type.isInterface()) {
        throw new IllegalArgumentException();
    }
    //No @SPI annotations are added, only those annotated @SPI will be resolved.
    if(!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException();
    }
    //Get the specified type of Extension Loader from the cache first
    //EXTENSION_LOADERS is a Concurrent HashMap that caches all instances of Extension Loader that have been loaded.
    //For example, when you load Protocol.class here, you use Protocol.class as key and Extension Loader as value.
    //Each extension point to be loaded corresponds to only one Extension Loader instance, that is, only one Protocol.class exists in the cache.
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    //Cache does not exist
    if (loader == null) {
        //Create a new Extension Loader instance and put it in the cache
        //For each extension, there is only one corresponding Extension Loader instance in dubbo
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

The above code returns an Extension Loader instance, and the getExtension Loader (Protocol. class) step does not do any loading work, but obtains an instance of Extension Loader.

Construction Method of Extension Loader

The above is an Extension Loader instance, and then look at what we did when we constructed the instance. We found that there is only one private constructor in Extension Loader:

private ExtensionLoader(Class<?> type) {
    //Interface type
    this.type = type;
    //For extension types that are Extension Factory, set to null
    //GetAdaptive Extension Method to Get a Runtime Adaptive Extension Type
    //Each Extension can only have an @Adaptive type implementation, and if not, dubbo automatically generates a class
    //objectFactory is an attribute of type ExtensionFactory, which is mainly used to load the implementation of the type that needs to be injected.
    //objectFactory is mainly used for the injection step. See the instructions at injection time for details.
    //Keep in mind here that all non-Extension Factory types return is an Adaptive Extension Factory
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

It is not difficult to understand that Extension Factory is mainly used to load the implementation of injected classes. It is divided into SpiExtension Factory and Spring Extension Factory, which are used to load SPI extension implementation and Spring bean implementation respectively.

Acquisition adaptive implementation

After returning an example of Extension Loader above, the adaptive implementation is loaded, which is done by calling the getAdaptive Extension () method:

getAdaptiveExtension()-->
                createAdaptiveExtension()-->
                                getAdaptiveExtensionClass()-->
                                                getExtensionClasses()-->
                                                                loadExtensionClasses()

First, look at the getAdaptive Extension () method, which is used to obtain an extended adaptive implementation class. Finally, the adaptive implementation class returned is a class named Protocol$Adaptive, and this class implements the Protocol interface:

public T getAdaptiveExtension() {
    //Find the instance object from the instance cache first
    //private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
    //A Holder instance is stored in the current Extension Loader to cache instances of adaptive implementation classes
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {//Cache does not exist
        if(createAdaptiveInstanceError == null) {
            synchronized (cachedAdaptiveInstance) {
                //Check again if the cache already exists after acquiring the lock
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        //If not in the cache, create a new Adaptive Extension instance
                        instance = createAdaptiveExtension();
                        //New instances add caching
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {createAdaptiveInstanceError = t; }
                }
            }
        }
    }

    return (T) instance;
}

Create adaptive extensions

There are no instances of adaptive extensions in the cache, which means that no instances of adaptive extensions have been created. The next step is to create an implementation of adaptive extensions, the createAdaptive Extension () method, which is used to create instances of adaptive extensions classes:

private T createAdaptiveExtension() {
    try {
        //Get the Adaptive Extension Class first through getAdaptive Extension Class
        //Then get its instance
        //Finally, the injection process is carried out.
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {}
}

Acquisition of adaptive extended classes

Next, look at the getAdaptive Extension Class () method to get an adaptively extended Class that will be instantiated in the next step:

private Class<?> getAdaptiveExtensionClass() {
    //Load all implementations of the current Extension (Protocol, for example, loads only all implementation classes of Protocol), and if there is an implementation class of @Adaptive type, it is assigned to cachedAdaptiveClass.
    //Currently, only two implementation classes, Adaptive Extension Factory and Addressive Compiler, are annotated with @Adaptive
    //In addition to Extension Factory and Compiler type extensions, other types of extensions are dynamically created as follows
    getExtensionClasses();
    //After loading all the implementations, it is found that cachedAdaptiveClass is not empty
    //That is to say, the currently acquired adaptive implementation classes are either Adaptive Extension Factory or Adaptive Compiler, and they are returned directly. These two classes are of special use, instead of code generation, they are ready-made code.
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    //Failed to find an implementation of the Adaptive type, create one dynamically
    //For example, Protocol's implementation class, none of the implementations are annotated with @Adaptive, only the methods of the Protocol interface are annotated.
    //At this point, we need to generate the Protocol$Adaptive dynamically.
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

Loading Extended Class Implementation

First look at the getExtensionClasses() method to load the implementation of all extension classes:

private Map<String, Class<?>> getExtensionClasses() {
    //Obtained from the cache, cachedClasses is also a Holder, where Holder holds a Map, key is the extension point implementation name, and value is the extension point implementation class.
    //The implementation classes of all extension points of the current extension point type are stored here.
    //For example, Protocol stores all the implementation classes of Protocol.
    //For example, key is dubbo, value is com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
    //The implementation class corresponding to the implementation name of the cachedClasses extension point
    Map<String, Class<?>> classes = cachedClasses.get();
    //If it's null, that means it's not loaded, it's loaded, and it's only loaded this time.
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                //If no Extension implementation has been loaded, scan and load it and cache it after completion.
                //For each extension point, the load of its implementation will only be executed once
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

Look at the loadExtensionClasses() method, which loads the implementation class of the extension point:

private Map<String, Class<?>> loadExtensionClasses() {
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if(defaultAnnotation != null) {
        //The default implementation name of the current Extension
        //For example, the Protocol interface, annotated with @SPI("dubbo")
        //Here dubbo is the default value
        String value = defaultAnnotation.value();
        //There can only be a default name, if more, no one knows which one to use to achieve.
        if(value != null && (value = value.trim()).length() > 0) {
            String[] names = NAME_SEPARATOR.split(value);
            if(names.length > 1) {
                throw new IllegalStateException();
            }
            //Save the default name
            if(names.length == 1) cachedDefaultName = names[0];
        }
    }

    //Let's start loading the extended implementation class from the configuration file
    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    //Load from META-INF/dubbo/internal directory
    loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    //Load from META-INF/dubbo/directory
    loadFile(extensionClasses, DUBBO_DIRECTORY);
    //Load from META-INF/services/
    loadFile(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}

Load implementation classes from configuration files at various locations. For Protocol, the file loaded is a file named com.alibaba.dubbo.rpc.Protocol. The content of the file is (there are several configuration files with the same name, which are written directly together here):

registry=com.alibaba.dubbo.registry.integration.RegistryProtocol

filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol

dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol

hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol

com.alibaba.dubbo.rpc.protocol.http.HttpProtocol

injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol

memcached=memcom.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol

redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol

rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol

thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol

com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol

Look at the loadFile() method:

private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
    //Name of configuration file
    //Here type is an extension class, such as the com.alibaba.dubbo.rpc.Protocol class
    String fileName = dir + type.getName();
    try {
        Enumeration<java.net.URL> urls;
        //Get the class loader
        ClassLoader classLoader = findClassLoader();
        //Get all the files corresponding to the configuration file name
        if (classLoader != null) {
            urls = classLoader.getResources(fileName);
        } else {
            urls = ClassLoader.getSystemResources(fileName);
        }
        if (urls != null) {
            //Traversing files for processing
            while (urls.hasMoreElements()) {
                //Configuration file path
                java.net.URL url = urls.nextElement();
                try {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
                    try {
                        String line = null;
                        //One line at a time
                        while ((line = reader.readLine()) != null) {
                            //# Notes after the number
                            final int ci = line.indexOf('#');
                            //Remove comments
                            if (ci >= 0) line = line.substring(0, ci);
                            line = line.trim();
                            if (line.length() > 0) {
                                try {
                                    String name = null;
                                    //= The number is preceded by an extension name and followed by a fully qualified name for an extension class implementation.
                                    int i = line.indexOf('=');
                                    if (i > 0) {
                                        name = line.substring(0, i).trim();
                                        line = line.substring(i + 1).trim();
                                    }
                                    if (line.length() > 0) {
                                        //Implementation of Loading Extended Classes
                                        Class<?> clazz = Class.forName(line, true, classLoader);
                                        //See if the type matches
                                        //type is the Protocol interface
                                        //clazz is the implementation classes of Protocol
                                        if (! type.isAssignableFrom(clazz)) {
                                            throw new IllegalStateException();
                                        }
                                        //If the implementation class is of @Adaptive type, it is assigned to cachedAdaptiveClass, which holds the implementation class annotated by @Adaptive.
                                        if (clazz.isAnnotationPresent(Adaptive.class)) {
                                            if(cachedAdaptiveClass == null) {
                                                cachedAdaptiveClass = clazz;
                                            } else if (! cachedAdaptiveClass.equals(clazz)) {
                                                throw new IllegalStateException();
                                            }
                                        } else {//Either the @Adaptice type class or the implementation class without the annotation @Adaptive
                                            try {//Determine whether it is wrapper type
                                                //If the parameters in the constructor of the resulting implementation class are of the extended point type, it is a Wrapper class.
                                                //For example, ProtocolFilterWrapper implements the Protocol class.
                                                //It is constructed by such a public ProtocolFilter Wrapper (Protocol protocol)
                                                //It means that this class is a wrapper class.
                                                clazz.getConstructor(type);
                                                //CachedWrapper Classes are used to store wrapper classes in the current extension point implementation class
                                                Set<Class<?>> wrappers = cachedWrapperClasses;
                                                if (wrappers == null) {
                                                    cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                                                    wrappers = cachedWrapperClasses;
                                                }
                                                wrappers.add(clazz);
                                            } catch (NoSuchMethodException e) {
                                            //Without the constructor mentioned above, it is not wrapper type.
                                                //Obtaining parametric-free structures
                                                clazz.getConstructor();
                                                //Without a name, there is no XXXX = xxxx. com. XXX in the configuration file.
                                                if (name == null || name.length() == 0) {
                                                    //Find the value configured in the @Extension annotation
                                                    name = findAnnotationName(clazz);
                                                    //If you haven't found the name, get it from the class name
                                                    if (name == null || name.length() == 0) {
                                                        //For example, clazz is a Dubbo Protocol and type is a Protocol.
                                                        //The name you get here is dubbo.
                                                        if (clazz.getSimpleName().length() > type.getSimpleName().length()
                                                                && clazz.getSimpleName().endsWith(type.getSimpleName())) {
                                                            name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
                                                        } else {
                                                            throw new IllegalStateException(");
                                                        }
                                                    }
                                                }
                                                //It is possible to configure multiple names
                                                String[] names = NAME_SEPARATOR.split(name);
                                                if (names != null && names.length > 0) {
                                                    //Is it an Active-type class?
                                                    Activate activate = clazz.getAnnotation(Activate.class);
                                                    if (activate != null) {
                                                        //The first name serves as the key and is cached in the map cached by cached Activates
                                                        cachedActivates.put(names[0], activate);
                                                    }
                                                    for (String n : names) {
                                                        if (! cachedNames.containsKey(clazz)) {
                                                            //Put it in the cache of Extension implementation class and name mapping, each class is valid only for the first name
                                                            cachedNames.put(clazz, n);
                                                        }
                                                        Class<?> c = extensionClasses.get(n);
                                                        if (c == null) {
                                                            //Put it into the extensionClasses cache, and multiple name s may correspond to one extensionClasses
                                                            extensionClasses.put(n, clazz);
                                                        } else if (c != clazz) {
                                                            throw new IllegalStateException();
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                } catch (Throwable t) { }
                            }
                        } // end of while read lines
                    } finally {
                        reader.close();
                    }
                } catch (Throwable t) { }
            } // end of while urls
        }
    } catch (Throwable t) { }
}

Loading all the implementations of the current Extension here is complete. Continue to return to getAdaptive Extension Class. After calling getExtension Classes (), it first checks whether the classes with @Adaptive annotations have been parsed and added to the cache. If so, return directly to the cachedAdaptive Class here. Now it can only be one of the Adaptive Extension Factories or the Adaptive Compiler. If not, it means that it is a common extension point, then create it dynamically, such as creating a Protocol$Adaptive.

Code for creating adaptive extension classes

Look at the createAdaptive Extension Class () method for dynamically creating adaptive extension classes:

private Class<?> createAdaptiveExtensionClass() {
    //Assembling code for adaptive extension point classes
    String code = createAdaptiveExtensionClassCode();
    //Get the class loader for the application
    ClassLoader classLoader = findClassLoader();
    //Get the compiler
    //dubbo defaults to javassist
    //Here we use the extension point mechanism to find the concrete implementation of Compiler.
    //Now you know what cached Adaptive Class means. If there are no two classes, Adaptive Extension Factory and Addressive Compiler, then you have to go through the loading process and generate the code of the extension point class. So, it's dead cycle.
    //When the implementation class of Compiler is parsed here, it is returned directly in getAdaptive Extension Class
    //You can look at the Adaptive Compiler class. If we don't specify it, we use javassist by default.
    //Here Compiler is an example of Javassist Compiler
    com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    //Converting code to Class
    return compiler.compile(code, classLoader);
}

Next, take a look at the createAdaptive Extension ClassCode () method, which assembles the code for the adaptive extension class (spelling source code, longer code is not listed). Here is the generated Protocol$Adaptive:

import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
  public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
    if (arg1 == null) throw new IllegalArgumentException("url == null");

    com.alibaba.dubbo.common.URL url = arg1;
    String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );

    if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");

    com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);

    return extension.refer(arg0, arg1);
  }

  public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
    if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");

    if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();

    String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );

    if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");

    com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);

    return extension.export(arg0);
  }

  public void destroy() {
    throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
  }

  public int getDefaultPort() {
    throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
  }
}

The generation of other specific extension points is similar. After generating the code, we find the ClassLoader, and then get the adaptive implementation of Compiler. Here we get the Adaptive Compiler, and finally call compiler.compile(code, classLoader); to compile the class generated above and return, we first enter the compile method of Adaptive Compiler:

public Class<?> compile(String code, ClassLoader classLoader) {
    Compiler compiler;
    //Get an Extension Loader
    ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
    //Default Compiler name
    String name = DEFAULT_COMPILER; // copy reference
    //If you specify a Compiler name, use the specified name to find the Compiler implementation class
    if (name != null && name.length() > 0) {
        compiler = loader.getExtension(name);
    } else {//Without specifying the Compiler name, look for the default Compiler implementation class
        compiler = loader.getDefaultExtension();
    }
    //Call specific implementation classes for compilation
    return compiler.compile(code, classLoader);
}

Gets an extension of the specified name

Let's first look at the implementation class loader.getExtension(name) for the extension based on the specific name; the loader is Extension Loader< Compiler> type. This is where it's more convenient than Java's SPI. Java's SPI can only be found by traversing all implementation classes, while dubbo can specify a name search. The code is as follows:

public T getExtension(String name) {
    if (name == null || name.length() == 0)
        throw new IllegalArgumentException("Extension name == null");
    //If name is specified as true, get the default implementation
    if ("true".equals(name)) {
        //The default implementation lookup is parsed below
        return getDefaultExtension();
    }
    //Holder is fetched from the cache first, cachedInstance is a Concurrent HashMap, the key is the extended name, and the value is a older class that holds an instance of the implementation class corresponding to the name.
    Holder&lt;Object&gt; holder = cachedInstances.get(name);
    //If the Holder corresponding to the current name does not exist, create one and add it to the map
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder&lt;Object&gt;());
        holder = cachedInstances.get(name);
    }
    //Get saved instances from Holder
    Object instance = holder.get();
    //If it does not exist, you need to find the implementation class according to this name and instantiate one.
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                //Cache does not exist, create instance
                instance = createExtension(name);
                //Add Cache
                holder.set(instance);
            }
        }
    }
    //Existence, return directly
    return (T) instance;
}

Create an extended instance, create Extension (name);:

private T createExtension(String name) {
    //GetExtension Classes loads all implementations of the current Extension
    //As resolved above, a Map is returned, the key is name, and the value is Class corresponding to name.
    //Find the corresponding Class by name
    Class&lt;?&gt; clazz = getExtensionClasses().get(name);
    //If the class does not exist at this time, the definition is not found in all the configuration files, and the exception is thrown.
    if (clazz == null) {
        throw findException(name);
    }
    try {
        //Get from the created instance cache
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        //If it doesn't exist, create a new instance and add it to the cache
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        //Attribute injection
        injectExtension(instance);
        //Wrapper Packaging
        Set&lt;Class&lt;?&gt;&gt; wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null &amp;&amp; wrapperClasses.size() &gt; 0) {
            for (Class&lt;?&gt; wrapperClass : wrapperClasses) {
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) { }
}

I'll talk about attribute injection and Wrapper wrapping. Here Compiler gets an instance of a specific implementation class with a specified name, and then calls the compile() method of the instance to compile the generated code.

Get the default extension implementation

If the specified name is not found in the Adaptive Compiler, the default extension implementation loader.getDefaultExtension();:

public T getDefaultExtension() {
    //First, load all extension implementations
    //When loading, the default name cachedDefaultName is set, which is specified in @SPI, such as @SPI("javassist") specified by Compiler, so this is javassist.
    getExtensionClasses();
    if(null == cachedDefaultName || cachedDefaultName.length() == 0
            || "true".equals(cachedDefaultName)) {
        return null;
    }
    //Look for extended implementations under the name javassist
    //The specific process has been parsed above.
    return getExtension(cachedDefaultName);
}

The process of compiling Class with javassist is not explained for the time being. Let's go on to look at the process.

 private T createAdaptiveExtension() {
    try {
        //First, get the Adaptive Extension Class through getAdaptive Extension Class (the above step has been parsed to get a Class of the adaptive implementation class)
        //Then get its instance, and new Instance takes it as an example.
        //Finally, inject Extension is processed by injection
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) { }
}

Extended Point Injection

Next up is the injection of extension points, injection Extension, the explanation of injection to see the top extension point automatic assembly (IOC) instructions, injection Extension method:

//The example here is Xxx $Adaptive
private T injectExtension(T instance) {
    try {
        //On the way to objectFactory, let's first look at the following analysis
        //The object Factory here is the Adaptive Extension Factory.
        if (objectFactory != null) {
            //A Method of Traversing Extended Class Instances
            for (Method method : instance.getClass().getMethods()) {
                //Processing only set methods
                //set starts with one parameter, public
                if (method.getName().startsWith("set")
                        &amp;&amp; method.getParameterTypes().length == 1
                        &amp;&amp; Modifier.isPublic(method.getModifiers())) {
                    //set method parameter type
                    Class&lt;?&gt; pt = method.getParameterTypes()[0];
                    try {
                        //Property names corresponding to setter methods
                        String property = method.getName().length() &gt; 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                        //Obtain from Extension Factory based on type and name information
                        //For example, in an extended implementation class, there will be set methods such as setProtocol.
                        //Here pt is Protocol and property is Protocol.
                        //Adaptive Extension Factory looks for the corresponding extended implementation class based on these two parameters
                        //Here you return Protocol$Adaptive
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {//Explain that the parameter of set method is the type of extension point to inject.
                            //Inject an adaptive implementation class for set method
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) { }
                }
            }
        }
    } catch (Exception e) {}
    return instance;
}

For the process of obtaining Extension in Adaptive Extension Factory, we first get the concrete implementation class of Extension Factory when we instantiate it, then traverse each Extension Factory implementation class and get Extension in each Extension Factory implementation class separately.

Here, we use SpiExtension Factory's method of obtaining extensions as an example, getExtension, which first determines whether a given class is an interface annotated with @SPI, then obtains Extension Loader according to the class, and loads adaptive extensions using the obtained Extension Loader.

The Origin of objectFactory

objectFactory has a private constructor in Extension Loader:

//When we call the static method getExtensionLoader, we trigger the instantiation of the ExtensionLoader class, which initializes static variables and blocks, then constructs blocks of code, and finally initializes constructors.
private ExtensionLoader(Class&lt;?&gt; type) {
    this.type = type;
    //Here you get an Adaptive Extension Factory
    //Obtain from Extension Factory based on type and name information
    //Acquisition implementation
    //Why use the object factory to get the corresponding implementation in the setter method?
    //Can't we get the adaptive implementation directly through spi? For example, Extension Loader. get Extension (pt);
    //Because the setter method may be a spi or a normal bean.
    //So you can't write to death at this time and get it through spi. There are other ways to get it and inject it.
    // There are two implementations in dubbo, one is the Extension Factory of spi and the other is spring's Extension Factory.
    //If there's anything else, we can customize Extension Factory
    //objectFactory is an instance of Adaptive Extension Factory
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

So far, the createAdaptiveExtension method is parsed, and then returns to the upper getAdaptiveExtension() method. When the adaptive extension instance is created, it is added to the cache of the cachedAdaptiveInstance and returned to the place where the call is made, an Xxx$Adaptive instance.

Here, the following code is parsed:

private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

Use after being extended

We get an instance of Protocol$Adaptive, and then we call it. For example, we call the refprotocol. refer (Class< T> type, URL url) method. Because refprotocol is a Protocol$Adaptive instance here, we call the refer method of this instance first, where the code of the instance is at the top:

//To look good, the code is streamlined and the package names are removed.
public Invoker refer(Class arg0, URL arg1) throws Class {
    if (arg1 == null) throw new IllegalArgumentException();

    URL url = arg1;
    String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );

    if(extName == null) throw new IllegalStateException();

    Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);

    return extension.refer(arg0, arg1);
  }

You can see here that the extension name is first obtained according to the parameters in the url, and then the default extension is used if there is no one in the url, and then the specific implementation is obtained according to the extension. As for getExtension(String name), which has been parsed once above, here again is a list of:

public T getExtension(String name) {
    if (name == null || name.length() == 0)
        throw new IllegalArgumentException("Extension name == null");
    //If name is specified as true, get the default implementation
    if ("true".equals(name)) {
        //The default implementation lookup is parsed below
        return getDefaultExtension();
    }
    //Holder is fetched from the cache first, cachedInstance is a Concurrent HashMap, the key is the extended name, and the value is a older class that holds an instance of the implementation class corresponding to the name.
    Holder&lt;Object&gt; holder = cachedInstances.get(name);
    //If the Holder corresponding to the current name does not exist, create one and add it to the map
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder&lt;Object&gt;());
        holder = cachedInstances.get(name);
    }
    //Get saved instances from Holder
    Object instance = holder.get();
    //If it does not exist, you need to find the implementation class according to this name and instantiate one.
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                //Cache does not exist, create instance
                instance = createExtension(name);
                //Add Cache
                holder.set(instance);
            }
        }
    }
    //Existence, return directly
    return (T) instance;
}

Create an extended instance, create Extension (name);:

private T createExtension(String name) {
    //GetExtension Classes loads all implementations of the current Extension
    //As resolved above, a Map is returned, the key is name, and the value is Class corresponding to name.
    //Find the corresponding Class by name
    //For example, name is Dubbo and Class is com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
    Class&lt;?&gt; clazz = getExtensionClasses().get(name);
    //If the class does not exist at this time, the definition is not found in all the configuration files, and the exception is thrown.
    if (clazz == null) {
        throw findException(name);
    }
    try {
        //Get from the created instance cache
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        //If it doesn't exist, create a new instance and add it to the cache
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        //Here's an example of a concrete implementation, such as DubboProtocol.
        //Attribute injection, which has been parsed above, is injected according to the setXxx method in the instance
        injectExtension(instance);
        //Wrapper Packaging
        //CachedWrapper Classes holds all Wrapper classes
        //CachedWrapper Classes are put in when loading extended implementation classes
        //Wrapper class description at the top extension point automatic packaging (AOP)
        Set&lt;Class&lt;?&gt;&gt; wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null &amp;&amp; wrapperClasses.size() &gt; 0) {
            for (Class&lt;?&gt; wrapperClass : wrapperClasses) {
                //For example, the instance before packaging is the DubboProtocol instance.
                //First use the constructor to instantiate the current wrapper class
                //Our DubboProtocol instance is already included in the wrapper class
                //Then inject Extension into the wrapper class, and the injection process is on top of it.
                //The last Instance returned is an instance of the wrapper class.
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        //Here is an example returned after all wrapping classes have been wrapped.
        return instance;
    } catch (Throwable t) { }
}

The obtained Extension is implemented by layer-by-layer wrapping extension, and then the wrapped refer method is called, which is the method in the concrete implementation.

So far, the process of calling refprotocol. refer (Class< T> type, URL url) method has been resolved.

The parsing of the getActivateExtension method will be added later.

Links to the original text

Posted by David-fethiye on Sun, 07 Jul 2019 15:46:59 -0700