The Difference and Principle Analysis between SPI Mechanism and JDK Mechanism of Dubbo

Keywords: Java Dubbo Spring JDK

Starting today, you will learn more about DUbbo.First, give a brief overview of DUbbo.

Summary

Dubbo is the core framework for SOA (Service Oriented Architecture) service governance solutions.Used for distributed calls with a focus on distributed governance.
Simply put, it can be divided into four roles.Provider s, Consumer s, registration centers, and monitoring centers.Register and subscribe to services through the registry, and monitor services through the monitoring center.
Core Functions

  • Remoting: Remote communications that provides an abstract encapsulation of a variety of NIO frameworks, including synchronous to asynchronous and request-response modes for information exchange.
  • Cluster: A service framework that provides transparent remote procedure calls based on interface methods, including multiprotocol support, as well as cluster support for soft load balancing, failure tolerance, address routing, dynamic configuration, and so on.
  • Registry: Service registration, based on the registry directory service, enables service consumers to dynamically find service providers, make addresses transparent, and allow service providers to smoothly add or reduce machines.

Dubbo Component Role

Provider: Service Provider Exposing Service
Consumer: Service consumer invoking remote service
Registry: Registry for Service Registration and Discovery
Monitor: A monitoring center that counts the number and time of service calls
Container: Service run container, common container is Spring container

Call relationships:

  1. The service container is responsible for starting, loading, and running the service provider
  2. Service providers register their services with the registry at startup.
  3. When service consumers start up, they subscribe to the registry for the services they need.
  4. The registry returns a list of service provider addresses to the consumer, and if there is a change, the registry will push the change data to the consumer based on a long connection.
  5. Service consumers, from the list of provider addresses, select a provider to invoke based on the Soft Load Balancing algorithm, and if the call fails, choose another call.
  6. Service consumers and providers, cumulative calls in memory and call times, regularly send statistics to the Monitoring Center Monitor every minute.

SPI(Service Provider Interfaces)

It is a set of API s provided by Java to be implemented or extended by third parties and can be used to enable framework extensions and replace components.In the JDK documentation, it explains as follows:

A service is a well-known set of interfaces and (usually abstract) classes. A service provider is a specific implementation of a service.

In object-oriented design, the recommendation between modules is based on interface programming, not hard-coding implementation classes, which is also a pluggable principle for module design.In order not to specify that implementation in the program when assembling a module, a mechanism for service discovery is required, and the SPI of jDK is to find a service implementation for an interface.

Java SPI is actually a dynamic loading mechanism implemented by interface-based programming + policy mode + configuration file combination.
It looks for a mechanism for service implementation for an interface.A little like the IOC idea, which is to move control of the assembly outside of the program, this mechanism is particularly important in modeled design, so its core idea is decoupling

Use scenarios

  • Database Driven Loading Interface Implements Class Loading
    JDBC loads drivers for different types of databases
  • Log Face Interface for Class Loading
    SLF4J loads log implementation classes from different providers
  • Spring
  • Dubbo

Instructions

  1. When the service provider provides a specific implementation of the interface, create a file named Interface Fully Qualified Name in the META-INF/service directory of the jar package that implements the fully qualified name of the class.
  2. The jar package in which the interface implementation class resides is placed in the classpath of the main program
  3. The main program dynamically loads the implementation template through java.util.ServiceLoader, and loads the class into the JVM by scanning the configuration file in the META-INF/services directory to find the fully qualified name of the implementation class
  4. The SPI implementation class must carry a construction method without parameters
public final class ServiceLoader<S> implements Iterable<S>
{

    private static final String PREFIX = "META-INF/services/";

    // Represents a loaded class or interface
    private final Class<S> service;
    // Class Loader for Locating, Loading, and Instantiating providers
    private final ClassLoader loader;
    // Access control context used when creating ServiceLoader
    private final AccessControlContext acc;
    // Cache providers, sorted in the order of instantiation
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    // Lazy Find Iterator
    private LazyIterator lookupIterator;

    //Reloading is equivalent to re-creating the ServiceLoader for new service providers to install into a running Java virtual machine
    public void reload() {
        //Empty all instantiated service providers in the cache
        providers.clear();
        //Create a new iterator that will find and instantiate service providers from scratch.
        lookupIterator = new LazyIterator(service, loader);
    }

    /**
    ** Private Constructor
    ** Create a service loader using the specified class loader and service
    ** If no class loader is specified, the system class loader is used, that is, the application class loader
    **/
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

   //Resolution Failure Handling Method
    private static void fail(Class<?> service, String msg, Throwable cause)
        throws ServiceConfigurationError
    {
        throw new ServiceConfigurationError(service.getName() + ": " + msg,
                                            cause);
    }

    private static void fail(Class<?> service, String msg)
        throws ServiceConfigurationError
    {
        throw new ServiceConfigurationError(service.getName() + ": " + msg);
    }

    private static void fail(Class<?> service, URL u, int line, String msg)
        throws ServiceConfigurationError
    {
        fail(service, u + ":" + line + ": " + msg);
    }

    //Resolve a line in the service provider configuration file
    //First remove the comment check, then save
    //Return to the next line number
    //Duplicate configuration items will not be saved
    private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
                          List<String> names)
        throws IOException, ServiceConfigurationError
    {
        String ln = r.readLine();
        if (ln == null) {
            return -1;
        }
        int ci = ln.indexOf('#');
        if (ci >= 0) ln = ln.substring(0, ci);
        ln = ln.trim();
        int n = ln.length();
        if (n != 0) {
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                fail(service, u, lc, "Illegal configuration-file syntax");
            int cp = ln.codePointAt(0);
            if (!Character.isJavaIdentifierStart(cp))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service, u, lc, "Illegal provider-class name: " + ln);
            }
            if (!providers.containsKey(ln) && !names.contains(ln))
                names.add(ln);
        }
        return lc + 1;
    }

  //Parse the configuration file, parse the specified url configuration file
  //Parsing with the parseLine method saves uninstantiated service providers to the cache.
    private Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError
    {
        InputStream in = null;
        BufferedReader r = null;
        ArrayList<String> names = new ArrayList<>();
        try {
            in = u.openStream();
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        } catch (IOException x) {
            fail(service, "Error reading configuration file", x);
        } finally {
            try {
                if (r != null) r.close();
                if (in != null) in.close();
            } catch (IOException y) {
                fail(service, "Error closing configuration file", y);
            }
        }
        return names.iterator();
    }

    //Iterator for service provider lookup
    private class LazyIterator implements Iterator<S>
    {
        //spi
        Class<S> service;
        //classloader
        ClassLoader loader;
        //Save url of implementation class
        Enumeration<URL> configs = null;
        //Save the full name of the implementation class
        Iterator<String> pending = null;
        //Full name of next implementation class in iterator
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    }
    //Get Iterator
    //Return iterators that traverse service providers
    //Load available service providers lazily
    //Lazy loading implements that parsing configuration files and instantiating service providers is done by the iterator itself
    public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

   //Use the specified class loader to create a ServiceLoader for the specified service
    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

   //Create a ServiceLoader using a class loader in the thread context
    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

   //Create a ServiceLoader for the specified service using the Extension Class Loader
   //Only service providers that have been installed into the current Java virtual machine can be found and loaded, and service providers in the application class path will be ignored
    public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        ClassLoader prev = null;
        while (cl != null) {
            prev = cl;
            cl = cl.getParent();
        }
        return ServiceLoader.load(service, prev);
    }

    /**
     * Returns a string describing this service.
     *
     * @return  A descriptive string
     */
    public String toString() {
        return "java.util.ServiceLoader[" + service.getName() + "]";
    }

}

ServiceLoader is not an instantiation, and it goes to read the specific implementation of the file.Instead, it waits until iterators are used to iterate through it, the corresponding configuration file is loaded for parsing, the hasNext method is called to load the configuration file for parsing, and the Next method is called to instantiate and cache.

Advantage
The advantage of using Java SPI mechanisms is decoupling, which allows the logic of assembly control for third-party service modules to be separated from, rather than coupled with, the caller's business code.Applications can enable framework extensions or alternative framework components based on actual business conditions.

shortcoming
Although ServiceLoader is also considered a delayed load, it can only be obtained by traversing all, that is, the implementation classes of the interface are all loaded and instantiated once.If you don't want to use some implementation class, it is also loaded and instantiated, which is wasteful.Getting an implementation class is not flexible enough, it can only be obtained as an Iterator, and it cannot be obtained from a parameter.
It is not safe for multiple concurrent multithreads to use instances of the ServiceLoader class.

SPI Mechanism of Dubbo

As you can see from the diagram, Dubbo is associated with extension points through Extension Loader when extending individual modules.
Extension points in Dubbo need to meet the following characteristics:

  1. Extension points must be of Interface type and must be commented by @SPI
  2. Configuration files are stored in META-INF/services/and META-INF/dubbo/and META-INF/dubbo/internal. The file name defined under these paths is the full class name of the extension point interface. The extension implementation of the extension point is configured as a key-value pair in the file, which is very different from the storage of JDk SPI, so ServiceLoader cannot be used directly in Dubbo.Instead, Extension Loader is used to load various configurable components in Dubbo, such as ProxyFactory, LoadBalance, RCP Protocol, Filter, Container, Cluster, and Registry types.
    Extensions defined in META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory:
adaptive = com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory 
spi = com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory 
spring = com.alibaba.dubbo.config.spring.extension.SpringExtensionFactor

These are used when identifying extension points, @SPI, @Adaptive, @Activate

@SPI (annotated on class): This annotation identifies that the interface is an extension point and the attribute value is used to specify the name of the default adapter extension point.
@Activate (annotated on types and methods):@Activate annotation on the implementation class of an extension point represents the condition under which an extension class is acquired, is acquired if it meets the condition, is not acquired if it does not, and is filtered according to the group and value attributes in @Activate.
@Adaptive (annotated on types and methods): If annotated on a class, this class is the default adapter extension.When annotating the method of an Extension Point Interface, dubbo dynamically generates an adapter extension class (Generate Code, Dynamically Compile Instantiated Class) for this Extension Point Interface named simple class name +$Adaptive.The purpose of this is to adapt different extension instances at runtime, get the name of the extension class to use from the URL at runtime by passing in parameters of the URL type or parameters containing the Get URL method internally, load the corresponding extension instance according to its name, and call the same method with this extension instance object.If the runtime does not have an extension instance adapted to the runtime, then the @SPI annotation is used to annotate the default specified extension.In this way, the runtime is adapted to the corresponding extensions.
We randomly find an interface defined in the source code: Transporter

@SPI("netty")
public interface Transporter {
    // Bind a server
    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;

    // Connect to a server, that is, create a client
    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;
}

ExtensionLoader dynamically generates a Transporter$Adaptive class through the createAdaptiveExtensionClassCode method, which generates the following code:

package com.alibaba.dubbo.remoting;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Transporter$Adaptive implements com.alibaba.dubbo.remoting.Transporter{
    
public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        //An exception is thrown if the URL parameter is empty.
        if (arg0 == null) 
            throw new IllegalArgumentException("url == null");
        
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
        
        (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        return extension.connect(arg0, arg1);
    }
    public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        if (arg0 == null) 
            throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
        (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        
        return extension.bind(arg0, arg1);
    }
}

These are template code, the core of which is a single line to get an extended instance object with the specified name.
com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);

Extension Loader

It controls the initialization and loading of extensions for all extension points.
Two static attributes are stored in the Extension Loader, EXTENSION_LOADERS holds the Extension Loader instance object corresponding to the open extension point of the kernel, and EXTENSION_INSTANCES holds the instance objects of the extension type (Class) and extension type.

private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);

    //This is the configuration file path in jdk's SPI extension mechanism, dubbo to be compatible with jdk's SPI
    private static final String SERVICES_DIRECTORY = "META-INF/services/";

    //User-defined extension implementation profile store path
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";

    //Extension implementation profile store path for dubbo internal provisioning
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");

    //Extend the loader collection, key is the extension interface, such as Protocol, etc.
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

    //Extend implementation collection, key is extension implementation class, value is extension object
    //For example, key is Class<DubboProtocol>, value is a DubboProtocol object
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
    //Extend interfaces, such as Protocol
    private final Class<?> type;

    //Object factory, which obtains instances of the extended implementation for injectExtension methods to inject instances of the extended implementation class into dependent attributes.
    //For example, if there is a Protocol protocol attribute in the StubProxyFactoryWrapper class, the implementation class instance of the Protocol is assigned by the set method
    private final ExtensionFactory objectFactory;

    //The extension mentioned below is the key value in the configuration file, such as "dubbo"

    //Cached extensions are mapped to extension classes and are exchanged with key s and value s of cachedClasses.
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();

    //Cached Extended Implementation Class Collection
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();

    //Mapping Extensions to Auto-Activated Classes with @Activate
    private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();

    //Cached collection of extended objects with key as extension and value as extension
    //For example, a Protocol extension with key dubbo and value DubboProcotol
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();

    //Cached Adaptive extension objects, such as objects of the AdaptiveExtensionFactory class
    private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();

    //A class of cached adaptive extension objects, such as the AdaptiveExtensionFactory class
    private volatile Class<?> cachedAdaptiveClass = null;

    //The default extension for the cache is the value set in @SPI
    private String cachedDefaultName;

    //Create cachedAdaptiveInstance exception
    private volatile Throwable createAdaptiveInstanceError;

    //Expand Wrapper Implementation Class Collection
    private Set<Class<?>> cachedWrapperClasses;

    //Mapping Extension Name to Load Exceptions Occurring in Corresponding Extension Classes
    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();

Extension Loader does not provide a public construct method, has a private construct method, gets the factory method of the Extension Loader instance, but provides a public static getExtension Loader.There are three more important methods in its public member method:
getActiveExtension: Get a conditional implementation of the current extension that can be automatically activated
getExtension: Gets the specified implementation of the current extension by name
getAdaptiveExtension: Get an adaptive implementation of the current extension

 private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
@SPI
public interface ExtensionFactory {
    <T> T getExtension(Class<T> type, String name);

}

From the above, you can see that ExtensionFactory is also an extension point, with two implementation classes: SpiExtensionFactory and AdagiveExtensionFactory, and actually a SpringExtensionFactory, where different implementation classes can load extension point implementations in different ways.If the extension point type to load is ExtensionFactory, the object is set to null.
In the default ExtensionFactory implementation, the AdaptiveExtensionFactory is annotated with the @Adaptive annotation, which means it is the corresponding adaptive extension implementation of the ExtensionFactory (there can be at most one adaptive implementation per extension point, and dubbo dynamically generates an adaptive implementation class if all implementations are not annotated with @Adaptive)

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    //A collection of extended objects that can be divided into dubbo SPI interface implementation class objects or Spring bean objects by default
    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        //Traverse through all supported extensions
        for (String name : loader.getSupportedExtensions()) {
            //Extended Objects Join Collection
            list.add(loader.getExtension(name));
        }
        //Returns a collection that cannot be modified
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            //Get Extended Objects by Extending Interfaces and Extensions
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}

The getSupportedExtensions method in the ExtensionLoader class is called in the code above, so the ExtensionLoader class is analyzed next.

 public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        //Extension point interface is empty, throwing exception
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        //Determine if type is an interface class
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        //Determining whether an interface is extensible
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }

        //Remove the extension loader corresponding to the extension interface from the extension loader collection
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);

        //If empty, create an extension loader for the extension interface and add it to EXTENSION_LOADERS
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            //Create Adapter Object
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            } else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }

As you can see from Extension Loader's private construction method, when you select Extension Factory, you do not use getExtension(name) to get a specific implementation class, but instead call getAdaptiveExtension to get an adaptive implementation.
First check if the cached adaptiveInstance exists and use it directly if it exists, otherwise call the createAdaptiveExtension method to create a new adaptiveInstance and cache it, that is, for an extension point, the same instance is obtained each time you call ExtensionLoader.getAdaptiveExtension.
Call getExtensionClasses() first in the call to getAdaptiveExtensionClass
In getAdaptiveExtensionClass(), call getExtensionClasses() to get an array of extended implementation classes and store it in the cachedClasses property.
From getExtensionClasses(), when cachedClasses is empty, load ExtensionClasses () is called
getExtensionClasses() loads all implementations of the current Extension, caches the cachedAdaptiveClass property if there is a @Adaptive type, and creates an AdaptiveExtensionClass dynamically if no @Adaptive type implementation is found.

First you get the value in the annotation for the extension point class, get the default value, and then read the information in the configuration file from a specific directory.
Finally, through loadClass, place the class in the extensionClasses variable

 private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }
 private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        //Cached Adaptive Extension Objects
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
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;
    }


private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            //Default value in @SPI
            String value = defaultAnnotation.value();
            if ((value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                //Only one default value is allowed
                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];
            }
        }

        //Load implementation class array from configuration file
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadDirectory(extensionClasses, DUBBO_DIRECTORY);
        loadDirectory(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }


private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
        //Splice interface fully qualified name to get complete file name
        String fileName = dir + type.getName();
        try {
            Enumeration<java.net.URL> urls;
            //Get ExtensionLoader class information
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                //Traverse files
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

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) {
                    //Skip commented content
                    final int ci = line.indexOf('#');
                    if (ci >= 0) line = line.substring(0, ci);
                    line = line.trim();
                    if (line.length() > 0) {
                        try {
                            String name = null;
                            int i = line.indexOf('=');
                            if (i > 0) {
                                //Split key and value according to'='
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                //Loading extension classes
                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                            }
                        } catch (Throwable t) {
                            IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                            exceptions.put(line, e);
                        }
                    }
                }
            } finally {
                reader.close();
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        //Whether the class implements an extension 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.");
        }
        //Determines if the class is an adapter for an extended interface
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            if (cachedAdaptiveClass == null) {
                cachedAdaptiveClass = clazz;
            } else if (!cachedAdaptiveClass.equals(clazz)) {
                throw new IllegalStateException("More than 1 adaptive class found: "
                        + cachedAdaptiveClass.getClass().getName()
                        + ", " + clazz.getClass().getName());
            }
        } else if (isWrapperClass(clazz)) {
            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
            }
            wrappers.add(clazz);
        } else {
            //Obtaining constructor objects by reflection
            clazz.getConstructor();
            //No extensions are configured, auto-generated, such as DemoFilter as demo, mainly for java SPI compatible configurations.
            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);
                }
            }
            // Gets the extension, which can be an array, with multiple topology extensions.
            String[] names = NAME_SEPARATOR.split(name);
            if (names != null && names.length > 0) {
                Activate activate = clazz.getAnnotation(Activate.class);
                //Join the cache if it is an automatically activated implementation class
                if (activate != null) {
                    cachedActivates.put(names[0], activate);
                }
                for (String n : names) {
                    if (!cachedNames.containsKey(clazz)) {
                        cachedNames.put(clazz, n);
                    }
                    //Cache Extension Implementation Class
                    Class<?> c = extensionClasses.get(n);
                    if (c == null) {
                        extensionClasses.put(n, clazz);
                    } else if (c != clazz) {
                        throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                    }
                }
            }
        }
    }

The above code completes the implementation and instantiation of the adaptive extension point type. The following method is the implementation of automatic injection of extension points. It obtains the corresponding parameter types and property names of all set methods that handle the current instance, queries from the Extension Factory based on these two conditions, and performs the injection operation if an extension point instance is returned.

private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                //Reflect to get all the methods in this class
                for (Method method : instance.getClass().getMethods()) {
                    //If it is a set method
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        /**
                         * Check {@link DisableInject} to see if we need auto injection for this property
                         */
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            //Get properties, such as Protocol protocol in the StubProxyFactoryWrapper class,
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            //Get property values, such as a Protocol object, or possibly a Bean object
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                //Injection Dependency Properties
                                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;
    }

Article Reference:
dubbo source 1: Extension Loader and get adapter class process resolution
Dubbo Extension Point Loading Mechanism - Extension Loader
[Dubbo]Adaptive

Posted by Boom.dk on Wed, 19 Feb 2020 10:42:21 -0800